在解释型语言内部运行:进攻性 Python 编程

admin 2025年1月26日16:40:39评论12 views字数 7936阅读26分27秒阅读模式

TrustedSec  Operating Inside the Interpreted: Offensive Python

简介

每隔一段时间,我都会有一种冲动,想要重新审视那些曾经流行但现在已经不受进攻社区青睐的旧技术。像 Office 宏、PowerShell 和自定义 shellcode 加载器这样的技术曾经非常有效,但现在被我交谈过的许多行业同事认为是"已烧毁"的。虽然这种说法有一定道理,但我和团队中的其他人仍然不断地用所谓"已烧毁"的战术技术程序 (TTPs) 在行动中取得成效,这让我们感到惊讶。

Python 恶意软件

在这篇文章中,我想重新审视另一个我认为是承载恶意软件负载的主要候选者的旧技术——Windows 版 Python。但在此之前,让我们先回顾一下这个领域的一些现有工作。

在解释型语言内部运行:进攻性 Python 编程

黑客似乎一直在编写 Python 恶意软件,从 Python 套接字 shell 到 Py2Exe 玩具植入程序,但我能记得使用的第一个 Python 技术是 Chris Truncer 的Veil Evasion 项目。Veil 最初于 2013 年发布,至今仍然像魔法一样——一个将 Python 转换为 shell 的工具。

不久之后,当微软宣布 PowerShell 将预装在 Windows 上时,恶意软件的黄金时代开始了。最初的 PowerShell empire、PowerSploit 和大量其他技术都是用 PowerShell 编写的,旨在在预装 PowerShell 的任何地方使用。Python 恶意软件退居二线,因为在使用之前必须在目标上安装 Python 解释器。之后,微软对 PowerShell 进行了一些重大更改,引入了反恶意软件扫描接口 (AMSI) 和各种日志记录功能,这使得开发 PowerShell 恶意软件变得更加困难。最终,进攻性工具开发人员放弃了 PowerShell,转而使用 C 和 C++ 等非托管语言。

2018 年底,微软在 Microsoft 商店 (当时称为 Windows 商店) 发布了 Python。我花了好几年才意识到它安装起来有多容易,我想很多人也没有意识到这一点。2022 年,Diego Capriotti 发布了Pyramid,这是一个用 Python 编写的利用框架。在我看来,这标志着现代 Python 技术的首次发布。大约在同时,Anthony Rose 发布了一个嵌入.NET 的 Python 3 引擎实现,灵感来自 Turla 的 IronNetInjector 代码。现在可以在 Windows 上运行 Python 代码,甚至不需要安装 Python 解释器。

随着 Python 在 Windows 上的易用性以及一些现有的 Python 技术,我认为在恶意软件开发领域中,进攻性 Python 有一个小但有意义的利基市场。

从 Microsoft 商店下载 Python

如前所述,可以轻松地从 Microsoft 商店下载 Python。在现代 Windows 系统上,只需打开 Microsoft 商店并搜索 Python。有几个版本可供选择。在撰写本文时,Python 3.7 到 3.13 都是可用的。只需点击"获取"按钮即可下载 Python。不需要管理员权限。或者,如果你知道要下载的包,Microsoft 商店有一个默认的协议处理程序。例如,可以使用以下 URL 打开 Python 3.13:ms-windows-store://pdp/?ProductId=9pnrbtzxmb4z

在解释型语言内部运行:进攻性 Python 编程

图 1 - Microsoft 商店中可用的 Python

安装后,可以从开始菜单或 C:UsersAppDataLocalMicrosoftWindowsApps 文件夹中的存根可执行文件运行 Python。

在解释型语言内部运行:进攻性 Python 编程

图 2 - 运行从 Microsoft 商店下载的 Python

离线下载和安装 Python

在某些情况下,我们可能无法使用 Microsoft 商店以这种方式下载 Python:

  • 我们只能使用命令行/无 GUI 访问权限
  • Microsoft 商店通过组策略或其他方式被禁用
  • 主机上没有直接的互联网连接

但不用担心,仍然有办法在 Windows 上加载 Python 或其他 Microsoft 商店包:

第 1 步:在浏览器中打开所需的 Microsoft 商店应用程序。

在解释型语言内部运行:进攻性 Python 编程

图 3 - 在浏览器中打开 Python 商店页面

第 2 步:复制 Microsoft 商店 URL 并将其粘贴到https://store.rg-adguard.net/。下载 MSIX、APPX 或 APPXBundle 文件,这是包安装程序。请注意,即使我们可能不信任这个第三方应用商店,安装程序包也是经过数字签名的,这可以防止篡改安装程序。即便如此,我建议在将包文件部署到目标之前,在实验环境中安装和测试包文件。

在解释型语言内部运行:进攻性 Python 编程

图 4 - 从 rg-adguard.net 获取 Python MSIX 文件

第 3 步:将安装程序文件传输到目标机器。如果可以使用 GUI 访问,只需双击文件即可启动安装过程。在后台,这会启动程序 AppInstaller.exe

在解释型语言内部运行:进攻性 Python 编程

图 5 - 双击 MSIX 安装提示

或者,可以使用 PowerShell Add-AppxPackage cmdletdism.exe来安装或卸载 Microsoft 商店包。

在解释型语言内部运行:进攻性 Python 编程

图 6 - 从 PowerShell 安装 MSIX 文件

在解释型语言内部运行:进攻性 Python 编程

图 7 - 显示 Python 应用程序安装

在解释型语言内部运行:进攻性 Python 编程

图 8 - 通过 PowerShell 卸载 Python

应该注意的是, dism.exe 需要管理员权限才能运行,即使包安装本身不需要它。

在解释型语言内部运行:进攻性 Python 编程

图 9 - 使用 dism.exe 从 MSIX 文件安装 Python

如果你不确定 MSIX 包将安装在哪里,我写了一个 Python 脚本,它试图在不安装的情况下预测与 Windows 应用包相关的主要二进制文件的安装路径。

在解释型语言内部运行:进攻性 Python 编程

图 10 - Python 和 iTunes 应用程序的预测可执行文件路径

注意 Python 的最终可执行文件位于:C:UserskevinclarkAppDataLocalMicrosoftWindowsAppsPythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0python3.13.exe

通常,这种格式是:C:Users[username]AppDataLocalMicrosoftWindowsApps[company_name].[app_name]_[publisher_id][executable].exe,其中 [publisher_id] 是从发布者证书生成的唯一哈希值,[executable] 是包安装程序文件中 AppxManifest.xml 文件中指定的主要可执行文件名。虽然有些包偏离了这种结构,但大多数包安装可以用这个公式预测。

在解释型语言内部运行:进攻性 Python 编程

Pip 包管理器

Microsoft 商店 Python 包附带安装了 Pip 包管理器,可以通过使用Python.exe -m pip来调用。如果可以访问 PyPI 存储库的网络,可以正常下载和安装包。

在解释型语言内部运行:进攻性 Python 编程

图 11 - 通过 Pip 安装库

但许多企业环境阻止访问 PyPI 存储库或有使下载包成为挑战的 Web 代理。这可能使进攻性 Python 的使用成为一个挑战。我们必须做以下两件事之一:

  • 使用只依赖标准库的 Python 工具
  • 离线下载所需的库并将其传输到主机

虽然很烦人,但可以在离线主机上传输然后安装包。我写了一个简单的离线包下载器和一个只使用标准库的配套安装程序。

在解释型语言内部运行:进攻性 Python 编程

图 12 - 离线包下载器帮助菜单

在解释型语言内部运行:进攻性 Python 编程

图 13 - 下载带依赖项的 Pip 轮子

下载包后,脚本将它们压缩成一个单独的文件,该文件可以直接上传到离线主机或托管在 Web 服务器上。然后使用安装脚本下载包的 ZIP 文件并安装每个 Pip 轮子。

在解释型语言内部运行:进攻性 Python 编程

图 14 - 离线包安装程序帮助菜单

在解释型语言内部运行:进攻性 Python 编程

图 15 - 从离线包的 ZIP 文件执行安装

Python 标准库

我们可以看到,可以在没有直接连接到 PyPI 存储库的主机上下载和安装库。否则,如果可能的话,尽量将进攻性工具限制在标准库包中仍然是一个好习惯。一个很好的例子是使用不太用户友好的 urllib而不是requests 库。

Python 标准库中内置了许多其他有用的功能。以下是一些进攻性开发人员可能想要做的事情及其实现所在的库列表:

功能

标准库模块

Web 请求

urllib

TCP 连接

socket

Sha256 哈希

hashlib

压缩

gzip, bz2, zlib, zipfile

加载 DLL 和调用非托管函数

ctypes

生成进程和运行命令

subprocess

处理结构化数据

json, xml

访问和修改注册表

winreg

编码和解码二进制数据

base64

IP 地址操作

ipaddress

但 Python 并不提供标准库中我们可能需要的所有东西。一些重要的功能需要使用库或需要手动实现:

功能

第三方库模块

AES 加密

pycryptodome

Web 套接字连接

aiohttp

加载 CLR 和与托管代码交互

pythonnet

数据包捕获

libpcap

LDAP 交互

ldap3

Windows 协议

impacket

这里可以找到Windows Python 安装默认附带的完整包列表。

用于非托管执行的 Ctypes

在解释型语言内部运行:进攻性 Python 编程

我最喜欢的 Python 模块之一是 ctypes。与 struct 库一起,ctypes 让Python程序员能够以C语言的风格编写代码,包括加载非托管库和导出、手动内存管理以及执行真正的指针操作。如果你熟悉C#的PInvoke,ctypes本质上是 Python 的相同功能。

那么,我们如何使用 ctypes 来调用 Win32 API 呢?有五个步骤:

  • 加载提供我们想要的函数的 DLL
  • 获取我们想要调用的函数的句柄
  • 定义函数原型,包括参数和返回类型
  • 将 Python 参数封送到 C 类型参数
  • 调用函数
  • (可选) 将返回值封送回 Python 类型以供 Python 代码使用

对于 Win32 DLL,你应该使用ctypes.windll.dllnamectypes.WinDLL("dllname")来加载 DLL。要从特定文件加载 DLL,使用ctypes.CDLL(r"C:pathtodll.dll")

kernel32   = ctypes.windll.kernel32             # Load kernel32.dllkernel32_2 = ctypes.WinDLL(“kernel32”)          # Load kernel32.dll againcustom_dll = ctypes.CDLL(r"C:testcustom.dll"# Load custom.dll

加载 DLL 后,你需要找到要调用的导出函数。在这个例子中,我将使用来自 kernel32.dll 的 CreateFileW API。DLL 导出可以通过名称或导出序号来查找。大多数程序员更倾向于通过名称导入,因为这种方式在不同 Windows 版本之间更容易且更可靠。

CreateFileW     = kernel32.CreateFileW # Get exported function handleCreateFileW_ord = kernel32[212]        # Get function by ordinal. Not recommended since these values can change between Windows versions

在获得函数指针后,我们需要定义函数原型,其中包括函数参数和返回类型。wintypes 子模块非常贴心地为我们定义了所有常见的 Windows 基本类型,所以一定要导入它。设置定义参数类型的argtypes列表,然后将restype值设置为函数应该返回的任何内容。对于CreateFileW,其参数和返回类型在MSDN 上有明确定义

# MSDN documentation for CreateFileW:## HANDLE CreateFileW(#  [in]           LPCWSTR               lpFileName,#  [in]           DWORD                 dwDesiredAccess,#  [in]           DWORD                 dwShareMode,#  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,#  [in]           DWORD                 dwCreationDisposition,#  [in]           DWORD                 dwFlagsAndAttributes,#  [in, optional] HANDLE                hTemplateFile#);#from ctypes import wintypes# ctypes function definition for CreateFileW:#CreateFileW.argtypes = [    wintypes.LPCWSTR,    # filename    wintypes.DWORD,      # desired access    wintypes.DWORD,      # share mode    wintypes.LPVOID,     # security attributes    wintypes.DWORD,      # creation disposition    wintypes.DWORD,      # flags and attributes    wintypes.HANDLE      # template file]CreateFileW.restype = wintypes.HANDLE

最后,我们需要创建一些数据来传递给 CreateFileW 函数,将数据封送为 wintypes,然后执行该函数。请注意,ctypes 会自动为我们完成 Python 类型和 C 类型之间的数据转换。

# Define the test datafilename = "test.txt"# Test file nameGENERIC_READ = 0x80000000GENERIC_WRITE = 0x40000000CREATE_ALWAYS = 2FILE_ATTRIBUTE_NORMAL = 0x80# Execute the function with test datahandle = CreateFileW(    filename, # Auto converted from Python str to wintypes.LPCWSTR    GENERIC_READ | GENERIC_WRITE,    0,    None, # Translates to NULL when passed to CreateFileW    CREATE_ALWAYS,    FILE_ATTRIBUTE_NORMAL,    None)

就是这样!一个名为 test.txt 的文件已通过 Python 的 ctypes 模块在当前目录下创建完成。

Ctypes 示例

以下是一个完整的 Python 程序代码,用于在 MessageBox 窗口中显示当前进程的 ID 和名称。

import ctypesfrom ctypes import c_char_p, c_uint32, c_void_p, create_string_bufferimport os.path# Windows API constantsMAX_PATH = 260MB_OK = 0x00000000MB_ICONINFORMATION = 0x00000040# Load DLLskernel32 = ctypes.windll.kernel32user32 = ctypes.windll.user32# Define function prototypesGetCurrentProcessId = kernel32.GetCurrentProcessIdGetCurrentProcessId.restype = c_uint32GetCurrentProcessId.argtypes = []GetModuleFileNameA = kernel32.GetModuleFileNameAGetModuleFileNameA.restype = c_uint32GetModuleFileNameA.argtypes = [c_void_p, ctypes.c_char_p, c_uint32]MessageBoxA = user32.MessageBoxAMessageBoxA.restype = ctypes.c_int32MessageBoxA.argtypes = [c_void_p, c_char_p, c_char_p, c_uint32]# Get process ID using Windows APIprocess_id = GetCurrentProcessId()# Get process name using Windows APIbuffer = create_string_buffer(MAX_PATH)path_length = GetModuleFileNameA(None, buffer, MAX_PATH)if path_length == 0:    raise Exception("Failed to get module filename")# Convert buffer to string and get just the filenamefull_path = buffer.value.decode('ascii')process_name = os.path.basename(full_path)message = f"Process Name: {process_name}nProcess ID: {process_id}"MessageBoxA(    None,    message.encode('ascii'),    b"Process Information",    MB_OK | MB_ICONINFORMATION)
在解释型语言内部运行:进攻性 Python 编程

图 16 - 在 Python 中弹出消息框

最后,我编写了一个简单的反射式 DLL 加载器作为 Python 恶意软件的一个更实用的示例。虽然这个加载器不足以用于实际操作,但它清楚地展示了用 Python 实现的攻击者技术的一个基本组件。

在解释型语言内部运行:进攻性 Python 编程

图 17 - 反射式 DLL 加载器帮助菜单

在解释型语言内部运行:进攻性 Python 编程

图 18 - 调用反射加载的 DLL 中的 DllMain

在解释型语言内部运行:进攻性 Python 编程

图 19 - 查找并执行导出的 Run 函数

Python 中的 IoC 指标

Python 是一个在全球企业网络中使用的合法应用程序。它具有良好的声誉,python.exe 二进制文件由微软和 Python 软件基金会签名。在工作站和服务器上也经常可以看到已安装的 Python,这使得完全不需要安装它。

在解释型语言内部运行:进攻性 Python 编程

图 20 - python.exe 的有效代码签名证书

与许多解释型语言一样,Python 具有与动态生成代码相关的特殊内存指标。默认的 Windows CPython 安装在执行任何 Python 代码之前就会创建未备份的可执行内存段和读写执行内存。

在解释型语言内部运行:进攻性 Python 编程

图 21 - 在 python.exe 中发现的读写执行内存

最重要的是,Python 是一种通用语言,这意味着它的用途几乎是无限的,从数据科学到系统管理,从视频游戏编程到 DevOps 编排工作等等。那么,python.exe 进程的正常行为是什么?python.exe 是否应该连接到互联网上的网站?socket 连接到其他系统呢?如何分配或修改内存段?python.exe是否应该消耗大量内存或 CPU?

这些问题的具体答案可以明确地回答为"也许"或"这取决于 Python 在做什么"。虽然并非不可能,但所有这些因素综合在一起,使得端点产品更难以建立正常行为的基线,也使得攻击者和操作人员更容易在 python.exe 中生存。

结论

Python 作为恶意软件部署平台的价值常常被低估。它易于安装,提供高质量的内置库,并且是操作的绝佳目标进程。它不会取代当前的非托管操作技术,但在其他技术不可行时提供了一个便捷和实用的替代方案。

原文始发于微信公众号(securitainment):在解释型语言内部运行:进攻性 Python 编程

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月26日16:40:39
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   在解释型语言内部运行:进攻性 Python 编程http://cn-sec.com/archives/3677980.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息