记一次py恶意样本分析实战
字数 3378 2025-11-08 17:49:25

Python恶意样本分析实战教学文档

文档概述

本教学文档基于一次真实的Python恶意样本分析实战,详细拆解了攻击者使用的多层混淆与反检测技术,并逐步演示了如何从高度混淆的Payload中还原出恶意代码的原始逻辑。文档将遵循分析师的实战步骤,并深入解释每个环节的技术原理和操作细节,旨在帮助安全研究人员掌握类似的Python恶意软件分析技能。

核心分析链路:
多层编码/压缩隐藏字符干扰compile+exec触发reverse + marshal.loads链式加载字节码还原

第一章:样本初步观察与核心API识别

1.1 初始Payload结构

分析始于一段嵌入在XML/HTML中的Python代码。初始Payload的核心特征是大量使用Python内置的编码和压缩模块,并最终通过compileexec函数执行。

关键代码模式识别:

# 样本中发现的典型模式
code_obj = compile(source_decoded, '<string>', 'exec')
exec(code_obj, globals_dict, locals_dict)

1.2 compileexec 函数深度解析

  • compile 函数:

    • 作用: 将源代码字符串编译为可执行的代码对象(code object)。
    • 关键参数:
      • source:源代码字符串。在恶意样本中,这通常是经过多层解码后得到的明文或半明文代码。
      • filename:代码所在的文件名。恶意样本常使用 '<string>' 以避免在错误信息中暴露真实路径。
      • mode:编译模式。
        • 'exec':用于编译模块或一段程序语句。
        • 'eval':用于编译单个表达式,并返回其结果。
        • 'single':用于编译单条交互式语句。
  • 执行链风险: compileexec 的组合使得攻击者能够动态地执行任意代码。样本通常会先通过一个复杂的解码链还原出真正的恶意源代码,然后编译并执行。

1.3 多层压缩编码识别与自动化解码

初始Payload经过了一系列的编码和压缩,顺序通常为:base64gzipbz2lzmazlib

  • 自动化解码脚本思路:
    1. 使用正则表达式匹配 base64.b64decode('...') 中的内容。
    2. 编写一个 deobfuscate 函数,对提取出的数据依次尝试上述各种解压算法。
    3. 循环执行“匹配-解码”过程,直到无法再匹配到 base64.b64decode 模式为止,此时意味着我们可能已经剥离了一层“外壳”。
import re
import base64
import gzip
import bz2
import lzma
import zlib

def deobfuscate(encoded_data):
    # 尝试多种解压方式
    try:
        # 1. 先进行base64解码
        decoded = base64.b64decode(encoded_data)
        # 2. 尝试用各种压缩库解压
        for module in [gzip, bz2, lzma, zlib]:
            try:
                if module is zlib:
                    return module.decompress(decoded).decode('utf-8')
                else:
                    with module.open(fileobj=io.BytesIO(decoded)) as f:
                        return f.read().decode('utf-8')
            except Exception:
                continue
        # 如果都无法解压,尝试直接解码为字符串
        return decoded.decode('utf-8')
    except Exception as e:
        print(f"Deobfuscation error: {e}")
        return None

payload = "初始的混淆Payload字符串"
while True:
    match = re.search(r"base64\.b64decode$'([^']*)'$", payload)
    if not match:
        break
    encoded_str = match.group(1)
    new_payload = deobfuscate(encoded_str)
    if new_payload is None:
        break
    payload = new_payload
    print(f"Decoded payload length: {len(payload)}")
# 循环结束后,payload 可能进入下一阶段

第二章:中级对抗技巧分析与绕过

2.1 隐藏字符处理

在自动化解码链结束后,得到的Payload可能看起来变短了,但在可视化检查时发现异常。

  • 问题发现:

    • 打印 len(payload) 发现长度与肉眼所见字符数不符。
    • 将Payload转换为十六进制(payload.hex())或直接输出,可能会发现大量不可见的控制字符、空白字符(如零宽空格)或非常规Unicode字符。
  • 解决方案:

    • 编写简单的替换脚本,将这些干扰字符移除或替换为普通字符。
    • 示例: cleaned_payload = re.sub(r'[\x00-\x1f\x7f-\x9f\u200b-\u200f\u202a-\u202e]', '', payload)

2.2 Marshal模块与代码对象反序列化

经过清理后,Payload进入了新的阶段:使用 marshal 模块。

  • Marshal模块是什么?

    • Python内置的一个用于序列化Python对象(特别是代码对象、内部类型)的模块。它主要用于Python解释器自身生成 .pyc 文件。
    • 警告: Marshal格式不保证跨Python版本兼容,且不适合处理不受信任的数据,因为它可以直接构造代码对象。
  • 样本中的常见模式:

    # 通常会对数据进行反转(reverse)以增加分析难度
    data = payload[::-1]  # 将字符串或字节串反转
    code_obj = marshal.loads(data)  # 从反转后的数据中加载代码对象
    

2.3 逆向Pyc文件结构

为了分析 marshal.loads 加载的代码对象,一个常见的思路是将其重构为一个有效的 .pyc 文件,然后使用反编译工具。

  • Pyc文件结构:

    1. Magic Number (4字节): 标识创建此pyc文件的Python版本。例如,b'\xf3\x0d\x0d\x0a' 对应 Python 3.13。
    2. Bit Field (4字节): 标志位。最低位为1表示使用哈希校验格式,为0表示使用时间戳格式。
    3. 后续8字节:
      • 时间戳格式: 4字节修改时间 + 4字节源文件大小。
      • 哈希格式: 8字节哈希值。
    4. Marshal数据: 序列化后的代码对象数据。
  • 伪造Pyc头:
    样本可能会伪造一个简单的头部,让工具能够识别。

    pyc_header = b'\xf3\x0d\x0d\x0a' + b'\x00' * 12  # Python 3.13 魔数 + 全0的时间戳/哈希区
    with open('output.pyc', 'wb') as f:
        f.write(pyc_header)
        f.write(marshal_data)  # 这里是经过反转等处理后的数据
    
  • 遇到的挑战: 反编译工具(如 uncompyle6, pycdc)可能尚未支持最新的Python版本(如实战中的3.13),导致无法直接反编译出源码。

第三章:高级分析与源码还原

3.1 链式Marshal加载与代码对象提取

当反编译工具失效时,需要采用更底层的分析方法。分析师发现,恶意样本的代码对象(code_obj)的常量池(co_consts)中,存储着下一个阶段的Payload。

  • 动态提取循环:
    import marshal
    import dis  # Python反汇编模块
    
    # 假设 initial_data 是经过之前步骤处理后的字节串
    data = initial_data
    try:
        while True:
            # 1. 反转数据
            data = data[::-1]
            # 2. 加载为代码对象
            code_obj = marshal.loads(data)
            # 3. 反汇编当前代码对象,查看其指令
            print("Disassembly of current code object:")
            dis.dis(code_obj)
            print("\n" + "="*50 + "\n")
            # 4. 关键:从代码对象的常量池(co_consts)中提取第一个常量,它通常是下一阶段的载荷
            #    注意:co_consts 是一个元组,需要根据实际情况选择索引
            if code_obj.co_consts and isinstance(code_obj.co_consts[0], (bytes, str)):
                data = code_obj.co_consts[0]  # 更新data,继续循环
            else:
                break
    except Exception as e:
        print(f"Loop ended: {e}")
        # 最后出错的code_obj可能就是最内层的核心逻辑
    

通过这个循环,可以一层层地“剥开”恶意样本的外壳,直到最内层的恶意逻辑暴露出来。循环终止时,最后一个能被成功加载的 code_obj 通常就是核心功能模块。

3.2 字节码反汇编与人工/LLM辅助还原

对于无法反编译的Python版本,最后的还原手段是分析字节码。

  • 使用 dis 模块:
    dis.dis(code_obj) 可以将代码对象反汇编为人类可读的字节码指令。

  • 借助LLM(大语言模型)进行还原:

    1. 提取字节码:dis.dis(code_obj) 的输出文本保存下来。
    2. 提示工程: 向LLM提供清晰的指令。

      提示词示例:
      “你是一个资深的Python安全专家。请将以下Python字节码反汇编结果还原为等效的、可读性高的Python源代码。注意分析控制流和数据流。
      【此处粘贴dis.dis的输出】”

    3. 审计与校对: LLM的还原结果可能不完全准确,需要分析师凭借对Python字节码的理解进行人工校对,重点关注系统调用、文件操作、网络通信等敏感行为。

第四章:总结与防御建议

4.1 技术总结

本次分析的恶意样本巧妙地组合了多种技术:

  1. 混淆层: 多层压缩编码、隐藏字符。
  2. 执行层: 使用 compileexec 动态执行。
  3. 持久化/隐藏层: 利用 marshal 序列化代码对象,并通过链式加载和Pyc文件格式伪装,增加静态分析难度。
  4. 版本对抗: 采用较新的Python版本,利用反编译工具的滞后性。

4.2 检测与防御建议

  • 静态检测规则(IDS/YARA):

    • 关注代码中是否连续出现 base64gzipbz2lzmazlib 等模块的调用。
    • 检测 compile(..., '<string>', 'exec')exec(...) 的组合。
    • 监控对 __builtins____import__ 的修改操作。
    • 查找 marshal.loads(...) 以及字节串的 [::-1] 反转操作。
  • 动态沙箱分析:

    • 在隔离环境(沙箱、容器)中运行样本。
    • 钩住(Hook)关键函数(如 os.system, open, __import__, socket.connect),记录所有敏感操作。
    • 监控进程树和网络连接。
  • 供应链安全:

    • 严格审查第三方Python包。
    • 使用虚拟环境或容器限制应用的权限。

4.3 自动化分析脚本思路

建议将整个分析流程脚本化,形成一个自动化分析管道:

  1. 输入: 混淆的Payload。
  2. 循环解码: 自动进行多层编码/压缩解码。
  3. 字符清理: 自动移除隐藏字符。
  4. Marshal探测: 自动尝试反转并加载为代码对象,递归提取 co_consts
  5. 输出: 每层的解码结果、最终的反汇编代码、以及尝试还原的源码。同时记录各阶段的长度、哈希值,便于回溯分析。

文档说明: 本文档完全基于提供的链接内容进行整理、扩展和深化,未添加任何外部无关信息。所有技术细节均可在原文中找到对应或推导出的依据。

Python恶意样本分析实战教学文档 文档概述 本教学文档基于一次真实的Python恶意样本分析实战,详细拆解了攻击者使用的多层混淆与反检测技术,并逐步演示了如何从高度混淆的Payload中还原出恶意代码的原始逻辑。文档将遵循分析师的实战步骤,并深入解释每个环节的技术原理和操作细节,旨在帮助安全研究人员掌握类似的Python恶意软件分析技能。 核心分析链路: 多层编码/压缩 → 隐藏字符干扰 → compile+exec触发 → reverse + marshal.loads链式加载 → 字节码还原 第一章:样本初步观察与核心API识别 1.1 初始Payload结构 分析始于一段嵌入在XML/HTML中的Python代码。初始Payload的核心特征是大量使用Python内置的编码和压缩模块,并最终通过 compile 和 exec 函数执行。 关键代码模式识别: 1.2 compile 与 exec 函数深度解析 compile 函数: 作用: 将源代码字符串编译为可执行的代码对象(code object)。 关键参数: source :源代码字符串。在恶意样本中,这通常是经过多层解码后得到的明文或半明文代码。 filename :代码所在的文件名。恶意样本常使用 '<string>' 以避免在错误信息中暴露真实路径。 mode :编译模式。 'exec' :用于编译模块或一段程序语句。 'eval' :用于编译单个表达式,并返回其结果。 'single' :用于编译单条交互式语句。 执行链风险: compile 和 exec 的组合使得攻击者能够动态地执行任意代码。样本通常会先通过一个复杂的解码链还原出真正的恶意源代码,然后编译并执行。 1.3 多层压缩编码识别与自动化解码 初始Payload经过了一系列的编码和压缩,顺序通常为: base64 → gzip → bz2 → lzma → zlib 。 自动化解码脚本思路: 使用正则表达式匹配 base64.b64decode('...') 中的内容。 编写一个 deobfuscate 函数,对提取出的数据依次尝试上述各种解压算法。 循环执行“匹配-解码”过程,直到无法再匹配到 base64.b64decode 模式为止,此时意味着我们可能已经剥离了一层“外壳”。 第二章:中级对抗技巧分析与绕过 2.1 隐藏字符处理 在自动化解码链结束后,得到的Payload可能看起来变短了,但在可视化检查时发现异常。 问题发现: 打印 len(payload) 发现长度与肉眼所见字符数不符。 将Payload转换为十六进制( payload.hex() )或直接输出,可能会发现大量不可见的控制字符、空白字符(如零宽空格)或非常规Unicode字符。 解决方案: 编写简单的替换脚本,将这些干扰字符移除或替换为普通字符。 示例: cleaned_payload = re.sub(r'[\x00-\x1f\x7f-\x9f\u200b-\u200f\u202a-\u202e]', '', payload) 2.2 Marshal模块与代码对象反序列化 经过清理后,Payload进入了新的阶段:使用 marshal 模块。 Marshal模块是什么? Python内置的一个用于序列化Python对象(特别是代码对象、内部类型)的模块。它主要用于Python解释器自身生成 .pyc 文件。 警告: Marshal格式不保证跨Python版本兼容,且不适合处理不受信任的数据,因为它可以直接构造代码对象。 样本中的常见模式: 2.3 逆向Pyc文件结构 为了分析 marshal.loads 加载的代码对象,一个常见的思路是将其重构为一个有效的 .pyc 文件,然后使用反编译工具。 Pyc文件结构: Magic Number (4字节): 标识创建此pyc文件的Python版本。例如, b'\xf3\x0d\x0d\x0a' 对应 Python 3.13。 Bit Field (4字节): 标志位。最低位为1表示使用哈希校验格式,为0表示使用时间戳格式。 后续8字节: 时间戳格式: 4字节修改时间 + 4字节源文件大小。 哈希格式: 8字节哈希值。 Marshal数据: 序列化后的代码对象数据。 伪造Pyc头: 样本可能会伪造一个简单的头部,让工具能够识别。 遇到的挑战: 反编译工具(如 uncompyle6 , pycdc )可能尚未支持最新的Python版本(如实战中的3.13),导致无法直接反编译出源码。 第三章:高级分析与源码还原 3.1 链式Marshal加载与代码对象提取 当反编译工具失效时,需要采用更底层的分析方法。分析师发现,恶意样本的代码对象( code_obj )的常量池( co_consts )中,存储着下一个阶段的Payload。 动态提取循环: 通过这个循环,可以一层层地“剥开”恶意样本的外壳,直到最内层的恶意逻辑暴露出来。循环终止时,最后一个能被成功加载的 code_obj 通常就是核心功能模块。 3.2 字节码反汇编与人工/LLM辅助还原 对于无法反编译的Python版本,最后的还原手段是分析字节码。 使用 dis 模块: dis.dis(code_obj) 可以将代码对象反汇编为人类可读的字节码指令。 借助LLM(大语言模型)进行还原: 提取字节码: 将 dis.dis(code_obj) 的输出文本保存下来。 提示工程: 向LLM提供清晰的指令。 提示词示例: “你是一个资深的Python安全专家。请将以下Python字节码反汇编结果还原为等效的、可读性高的Python源代码。注意分析控制流和数据流。 【此处粘贴dis.dis的输出】” 审计与校对: LLM的还原结果可能不完全准确,需要分析师凭借对Python字节码的理解进行人工校对,重点关注系统调用、文件操作、网络通信等敏感行为。 第四章:总结与防御建议 4.1 技术总结 本次分析的恶意样本巧妙地组合了多种技术: 混淆层: 多层压缩编码、隐藏字符。 执行层: 使用 compile 和 exec 动态执行。 持久化/隐藏层: 利用 marshal 序列化代码对象,并通过链式加载和Pyc文件格式伪装,增加静态分析难度。 版本对抗: 采用较新的Python版本,利用反编译工具的滞后性。 4.2 检测与防御建议 静态检测规则(IDS/YARA): 关注代码中是否连续出现 base64 、 gzip 、 bz2 、 lzma 、 zlib 等模块的调用。 检测 compile(..., '<string>', 'exec') 和 exec(...) 的组合。 监控对 __builtins__ 、 __import__ 的修改操作。 查找 marshal.loads(...) 以及字节串的 [::-1] 反转操作。 动态沙箱分析: 在隔离环境(沙箱、容器)中运行样本。 钩住(Hook)关键函数(如 os.system , open , __import__ , socket.connect ),记录所有敏感操作。 监控进程树和网络连接。 供应链安全: 严格审查第三方Python包。 使用虚拟环境或容器限制应用的权限。 4.3 自动化分析脚本思路 建议将整个分析流程脚本化,形成一个自动化分析管道: 输入: 混淆的Payload。 循环解码: 自动进行多层编码/压缩解码。 字符清理: 自动移除隐藏字符。 Marshal探测: 自动尝试反转并加载为代码对象,递归提取 co_consts 。 输出: 每层的解码结果、最终的反汇编代码、以及尝试还原的源码。同时记录各阶段的长度、哈希值,便于回溯分析。 文档说明: 本文档完全基于提供的链接内容进行整理、扩展和深化,未添加任何外部无关信息。所有技术细节均可在原文中找到对应或推导出的依据。