COMMON TROJANING TASKS ON WINDOWS–WINDOWS上常见的特洛伊木马任务
当您部署特洛伊木马时,您可能希望使用它执行一些常见的任务:抓取按键情况、截图和执行 shellcode ,以便为像 CANVAS 或 Metasploit 这样的工具提供交互式会话。本章重点介绍在Windows系统上执行这些任务。我们将使用一些沙盒检测技术来确定我们是否可以在防病毒或取证沙盒中运行。这些模块将很容易修改,并且可以在第7章开发的特洛伊木马框架内工作。在后面的章节中,我们将探讨您可以使用特洛伊木马实现的提升权限技术。每种技术都有自己的挑战和被终端用户或防病毒解决方案发现的可能性。
我们建议您在植入特洛伊木马前,认真细心地模拟您的目标,以便您在活动目标上进行测试之前可以在实验室中测试模块。让我们从创建一个简单的键盘记录器开始。
有趣的键盘记录
Keylogging ,即使用隐藏程序来记录连续的击键,是本书中最古老的技巧之一,今天它仍然被用于各种级别的秘密行动中。攻击者仍然在使用它,因为它在捕获凭据或对话等敏感信息方面极其有效。
一个名为 PyWinHook 的优秀的 Python 库使我们能够轻松捕获所有键盘事件( https://pypi.org/project/pyWinhook/ )。 PyWinHook 是原 PyHook 库的一个分支,经过更新后可以支持 Python 3。它利用了所在机器的 Windows 函数 SetWindowsHookEx ,该函数允许我们安装一个用户定义的函数来调用某些 Windows 事件。通过挂载键盘事件的 hook (钩子),我们将能够捕获目标发出的所有按键。除此之外,我们还想知道他们执行这些击键的确切过程,这样我们就可以确定用户名、密码或其他有用信息何时被输入。
PyWinHook 为我们处理所有低层的编程,把按键记录器的核心功能逻辑留给我们。让我们打开 keylogger.py ,并看看它提供的一些管道:
1 | from ctypes import byref, create_string_buffer, c_ulong, windll |
好的。我们定义了一个常量, TIMEOUT ,创建了一个新的类, KeyLogger ,并编写了 get_current_process 类函数来获取活跃窗口及其相关的 process ID (进程标识)。在该函数中,我们首先调用 GetForeGroundWindow [1],它返回目标桌面上活跃窗口的句柄。接下来,我们将该句柄传递给 GetWindowThreadProcessId [2]函数,以检索窗口的进程标识。然后,我们打开进程[3],并使用生成的进程句柄,找到流程的实际可执行名称[4]。最后一步是使用 GetWindowTextA [5]函数抓取窗口标题栏的全文。在这个辅助函数的最后,我们输出所有的信息[6],这样您就可以清楚地看到哪些击键伴随着哪个进程和窗口。现在让我们把按键记录器的“肉”(功能函数)放在适当的位置来完成它:
1 | def mykeystroke(self, event): |
让我们把它分解一下,从 run 函数开始。在第7章中,我们创建了脆弱目标可以运行的模块。每个模块都有一个名为 run 的入口函数,所以我们编写这个键盘记录程序也是遵循相同的模式,并且我们可以以相同的方式使用它。第7章的命令和控制系统中的 run 函数没有任何参数,并返回其输出。为了匹配这里的行为,我们暂时将 stdout 切换到一个类似文件的对象 StringIO 。现在,写入 stdout 的所有内容都将转到该对象,我们稍后将查询该对象。
切换 stdout 之后,我们创建了 KeyLogger 对象,并定义了 PyWinHook HookManager [4]。接下来,我们将 KeyDown 事件绑定到 KeyLogger 回调函数 mykeystroke [5]。然后,我们指示 PyWinHook 挂钩所有按键[6],并继续执行,直到超时。每当目标按下键盘上的某个键时,我们的 mykeystroke 函数都会以事件对象作为参数进行调用。我们在 mykeystroke 中做的第一件事是检查用户是否更改了窗口[1],如果是,我们获取新窗口的名称和进程信息。然后我们看一下发出的按键情况[2],如果它在 ASCII 可打印范围内,我们就把它打印出来。如果它是一个修饰符(如 SHIFT 、 CTRL 或 ALT 键)或任何其他非标准按键,我们就从事件对象中获取键名。我们还检查用户是否正在执行粘贴操作[3],如果是的话,我们转储剪贴板的内容。回调函数通过返回 True 来结束,以允许链中的下一个钩子(如果有的话)处理事件。我们去试试吧!
Kicking the Tires
测试我们的键盘记录器很容易。只需运行它,然后开始正常使用Windows。尝试使用网络浏览器、计算器或任何其他应用程序,然后在终端中查看结果:
1 | C:\Users\tim>python keylogger.py |
您可以看到,我们在运行键盘记录脚本的主窗口中键入了 test 这个词。然后,我们启动了火狐浏览器,浏览了 nostarch.com ,并运行了一些其他应用程序。我们现在可以有把握地说,我们已经将我们的键盘记录器添加到了我们的特洛伊木马技巧包中!让我们继续做实现截图的任务。
捕捉屏幕截图
大多数恶意软件和渗透测试框架都包含对远程目标进行截屏的功能。这有助于捕获图像、视频帧或其他敏感数据,这些数据您可能无法通过数据包捕获或键盘记录器看到。幸运的是,我们可以使用 pywin32 包对所在机器的 Windows API 进行调用来获取它们。安装pip包:
1 | pip install pywin32 |
截屏捕获器将使用 Windows 的 Graphics Device Interface (GDI,图形设备接口)来确定必要的属性,如屏幕总大小,并捕获图像。有些截屏软件只会抓取当前活动窗口或应用程序的截图,但我们会抓取整个屏幕。让我们开始吧。打开 screenshotter.py ,并输入以下代码:
1 | import base64 |
让我们来回头看一下这个小脚本是做什么的。我们获得了整个桌面的句柄[2],它包括多个显示器上的整个可视区域。然后,我们确定屏幕(或多个屏幕)的大小[1],以便我们知道屏幕截图所需的尺寸。我们调用 GetWindowDC [3]函数创建一个设备上下文,并传入桌面的句柄。(在 Microsoft Developer Network [MSDN,微软开发者网络] 上了解更多关于设备上下文和 GDI 编程的信息)。接下来,创建一个基于内存的设备上下文[4],我们将在其中存储图像捕获,直到我们将位图字节写入文件。然后我们创建一个位图对象[5],它被设置为我们桌面的设备上下文。然后, SelectObject 调用将基于内存的设备上下文设置为指向我们正在捕获的位图对象。我们使用 BitBlt [6]函数获取桌面图像的逐位副本,并将其存储在基于内存的上下文中。可以把这看作是对 GDI 对象的 memcpy 调用。最后一步是将此图像转储到磁盘[7]。
这个脚本很容易测试:只需从命令行运行它,并检查您的目录下的 screenshot.bmp 文件。您也可以将这个脚本包含在您的 GitHub 命令和控制存储库(笔者注:参照前几章)中,因为在 run 函数[8]调用了 screenshot 函数来创建图像,然后读取并返回文件数据。
下一步让我们继续来执行 shellcode 。
Pythonic Shellcode 实现
(笔者注:Pythonic,符合Python风格,优雅的、地道的、整洁的,可以了解一下“the zen of python”)
可能有一天,您希望能够与您的目标机器之一进行交互,或者使用您最喜欢的渗透测试或漏洞利用框架中有趣的新漏洞利用模块。这通常(尽管不总是)需要某种形式的 shellcode 来实现。为了在不接触文件系统的情况下执行原始 shellcode ,我们需要在内存中创建一个缓冲区来保存 shellcode ,并使用 ctypes 模块创建一个指向该内存的函数指针。然后我们再调用函数。
在我们的例子中,我们将使用 urllib 从 web 服务器获取 base64 格式的 shellcode ,然后执行它。让我们开始吧!打开 shell_exec.py 并输入以下代码:
1 | from urllib import request |
我们通过调用 get_code 函数从 web 服务器中检索 base64 编码的 shellcode 来开始我们的主要模块[1]。然后我们调用 run 函数将 shellcode 写入内存并执行。
在 run 函数中,我们分配了一个缓冲区[5]来保存解码后的 shellcode 。接下来,我们调用 write_memory 函数将缓冲区写入内存[2]。
为了能够写入内存,我们必须分配我们需要的内存 ( VirtualAlloc ) ,然后将包含 shellcode 的缓冲区移动到分配的内存 ( RtlMoveMemory ) 中。为了确保无论我们使用32位还是64位 Python , shellcode 都可以运行,我们必须指定我们想要从 VirtualAlloc 返回的结果是一个指针,并且我们将给 RtlMoveMemory 函数的参数是两个指针和一个大小对象。我们通过设置 VirtualAlloc.restype 和 RtlMoveMemory.argtypes [3]来实现这一点。如果没有这一步,从 VirtualAlloc 返回的内存地址的宽度将与 RtlMoveMemory 期望的宽度不匹配。
在对 VirtualAlloc 的调用中[4],0x40参数指定内存应该设置为具有执行和读/写访问的权限;否则,我们将无法编写和执行 shellcode 。然后,我们将缓冲区移动到分配的内存中,并返回指向缓冲区的指针。回到 run 函数中, ctypes.cast 函数允许我们将缓冲区像函数指针一样执行[6],这样我们就可以像调用任何标准的 Python 函数一样调用 shellcode 。我们通过调用函数指针来完成它,然后让 shellcode 执行[7]。
Kicking the Tires
您可以手工编写一些 shellcode ,或者使用您最喜欢的渗透测试框架(如 CANVAS 或 Metasploit )来为您生成它。因为 CANVAS 是一个商业工具,所以来看看这个生成 Metasploit payload 的辅助教程: http://www.offensive-security.com/metasploit-unleashed/Generating_Payloads/。我们用 Metasploit payload 生成器(在我们的例子中是 msfvenom )挑选了一些 Windows x86 的 shellcode 。在您的 Linux 机器上的 /tmp/shellcode.raw 中创建原始的 shellcode ,如下所示:
1 | msfvenom -p windows/exec -e x86/shikata_ga_nai -i 1 -f raw cmd=calc.exe > shellcode.raw |
我们用 msfvenom 创建 shellcode ,然后使用标准的 Linux 命令 base64
对其进行 base64 编码。下一个小技巧是使用 http.server 模块将当前工作目录(在我们的例子中是 /tmp/ )作为其 web 根目录。对端口8100上的任何对文件的 HTTP 请求将自动为您服务。现在把你的 shell_exec.py 脚本放到你的 Windows 机器上,然后运行它。您应该会在您的 Linux 终端中看到以下内容:
1 | 192.168.112.130 - - [12/Jan/2014 21:36:30] "GET /shellcode.bin HTTP/1.1" 200 - |
这表明您的脚本已经从使用 http.server 模块设置的 web 服务器中检索到了 shellcode 。如果一切顺利,您将收到一个反弹到您的框架的 shell ,并且已经弹出了 calc.exe ,获得了一个反向 TCP shell ,显示了一个消息框,或者编译了您的 shell 代码。
Sandbox Detection–沙箱检测
反病毒解决方案越来越多地采用某些形式的沙箱来确定可疑样本的行为。不管这个沙箱是越来越流行在网络边界运行,还是在目标机器本身上运行,我们都必须尽最大努力避免让目标网络上的任何防御措施得逞。
我们可以使用一些指示器来尝试确定我们的特洛伊木马是否在沙箱中执行。我们将监控目标机器最近的用户输入。然后我们将添加一些基本的智能功能来查找键盘按键、鼠标点击和双击情况。一台典型的机器在启动的当天有许多用户交互,而沙箱环境通常没有用户交互,因为沙箱通常被用作自动恶意软件分析技术。
我们的脚本还将尝试确定沙箱的“操作者”是否在重复发送输入(例如,可疑的、快速的连续鼠标点击)来响应并欺骗基本的沙箱检测方法。最后,我们将比较用户最后一次与机器交互的时间和机器已经运行了多长时间,这应该会让我们很好地知道我们是否在沙箱中。
然后,我们可以决定是否继续执行。让我们开始研究一些沙箱检测代码。打开 sandbox_detect.py 并输入以下代码:
1 | from ctypes import byref, c_uint, c_ulong, sizeof, Structure, windll |
我们定义必要的导入,并创建一个 LASTINPUTINFO 结构,该结构将保存系统上最后一次检测到输入事件的时间戳(以毫秒为单位)。接下来,我们创建一个函数 get_last_input 来确定输入的最后时间。请注意,在进行调用之前,您必须将 cbSize [1]变量初始化为结构的大小。然后我们调用 GetLastInputInfo 函数,该函数用时间戳填充 struct_lastinputinfo.dwTime 字段。下一步是通过调用 GetTickCount [2]函数来确定系统已经运行了多长时间。经过的时间是机器运行的总时间减去最后一次输入的时间。代码的最后一个小片段[3]是简单的测试代码,您可以运行脚本,然后移动鼠标或者按键盘上的一个键,就可以看到这段新代码正在运行的情况。
值得注意的是,总运行系统时间和最后检测到的用户输入事件可能会因您的具体植入方法而异。例如,如果您使用网络钓鱼策略植入了 payload ,用户很可能必须点击链接或执行其他操作来被感染。这意味着在最后一两分钟内,您将看到用户输入。但是,如果您看到机器已经运行了10分钟,并且最后一次检测到的输入是在10分钟前,那么您很可能在一个没有处理任何用户输入的沙箱中。这些判断的使用都是完成一个优秀的、可持续工作的木马的一部分。
您可以在轮询系统以查看用户是否空闲时使用同样的技术,因为您可能希望仅在用户积极持续使用机器时进行截图。同样,您可能希望仅在用户可能下线时传输数据或执行其他任务。例如,您还可以跟踪一段时间内的用户,以确定他们通常在线的日期和时间。
记住这一点,让我们定义三个阈值,在确定我们不再处于沙箱中之前,我们必须检测这些用户输入值中的多少。删除最后三行测试代码,并添加一些额外的代码来查看键盘击键和鼠标点击情况。这次我们将使用纯 ctypes 解决方案,而不是 PyWinHook 函数。您也可以很容易地使用 PyWinHook 来实现该目的,但是在您的工具箱中有几个不同的技巧方法总是有帮助的,因为每个防病毒和沙盒技术都有自己发现这些技巧的方法。让我们开始编码吧:
1 | class Detector: |
我们创建一个 Detector 类,并将点击和击键初始化为零。get_key_press 类函数告诉我们鼠标点击的次数、鼠标点击的时间以及目标发出的击键次数。它是通过迭代有效输入键的范围来生效[1];对于每个键,我们使用 GetAsyncKeyState [2]函数调用来检查它是否被按下。如果键的状态显示它被按下( state & 0x0001 的结果是真 ),我们检查它的值是否是0x1 [3],这是鼠标左键点击的虚拟键代码。我们增加鼠标点击的总次数,并返回当前的时间戳,以便我们稍后可以执行计时计算。我们还会检查键盘上是否有 ASCII 按键[4],如果有,只需增加检测到的按键总数。
现在让我们将这些函数的结果合并到我们的主沙箱检测循环中。向 sandbox_detect.py 添加以下函数:
1 | def detect(self): |
好吧。请注意这些代码块中的缩进!我们首先定义了一些变量[1]来跟踪鼠标点击的时间,并定义了三个阈值,分别是我们关注的按键次数、鼠标点击次数或双击的次数,这些都是在确定自己是在沙箱之外运行之前完成。我们在每次运行时都会随机化这些阈值,但是您当然可以根据自己的测试情况设置自己的阈值。
然后,我们检索自某种形式的用户输入在系统上注册以来经过的时间[2],如果我们觉得已经很久没有看到输入了(基于感染目标是如何作用的,如前所述),我们就退出,特洛伊木马就会“死亡”。这时您的特洛伊木马不会死在这里,而是可以执行一些无害的活动,例如读取随机注册表项或检查文件。在我们通过这个初始检查之后,我们继续我们基本的按键和鼠标点击检测循环。
我们首先检查按键或鼠标点击[3],并且知道如果函数返回了一个值,该值是发生按键或鼠标点击的时间戳。接下来,我们计算鼠标点击之间经过的时间[4],然后将其与我们的阈值进行比较[5],以确定这是否是双击。除了双击检测之外,我们还在检测沙箱操作者是否已经将点击事件[6]的流传输到沙箱中,试图欺骗沙箱检测技术。例如,在正常的计算机使用过程中,连续看到100次双击会很奇怪。如果已经达到双击的最大次数,并且它们连续快速发生[7],我们就退出程序。我们的最后一步是看一下我们是否通过了所有的检查,并达到了我们设置的最大点击次数,按键次数和双击次数[8];如果是这样的话,我们就退出我们的沙盒检测功能(笔者注:看代码可以知道这个时候就退出检测循环了)。
我们鼓励您调整和使用设置,并添加额外的功能,如虚拟机检测。统计你拥有的几台电脑(我们指的是你自己实际拥有的电脑,而不是你入侵控制的电脑)上鼠标点击、双击和按键的典型用法可能是值得的,看看你觉得巧妙的地方在哪里。根据您的目标,您可能想要更针对性的设置,或者您可能根本不关心沙盒检测。
您在本章中开发的工具可以作为在您设计实现的特洛伊木马中功能的基础层,并且由于我们的木马框架的模块化,您可以选择部署其中的任何一个。