4. PE文件节区分析

任务目标

分析给定PE文件的节区表,提取每个节区的名称、虚拟地址、虚拟大小、原始数据大小、权限属性,并计算熵值。通过节区名称异常(如UPX0、.aspack)、权限异常(可写且可执行)以及高熵值(接近8.0),判断样本是否加壳或存在可疑代码。

环境与依赖

  • Python 3.8+
  • 安装依赖:pip install pefile
  • 测试样本:任意PE文件(本案例使用一个模拟的加壳样本sample.exe,仅用于演示代码逻辑)

核心实现

以下代码使用pefile库解析PE节区,计算每个节区的熵值,并输出异常标记。熵值计算基于节区原始数据的字节频率分布,公式为:H = -Σ(p_i * log2(p_i)),其中p_i为字节值i出现的概率。

import pefile
import math
import collections

def calculate_entropy(data):
    """计算字节序列的香农熵"""
    if not data:
        return 0.0
    counter = collections.Counter(data)
    length = len(data)
    entropy = 0.0
    for count in counter.values():
        p = count / length
        entropy -= p * math.log2(p)
    return entropy

def analyze_sections(file_path):
    """分析PE文件节区,返回节区信息列表和异常标记"""
    pe = pefile.PE(file_path)
    results = []
    anomalies = []

    for section in pe.sections:
        name = section.Name.rstrip(b'\x00').decode('utf-8', errors='replace')
        raw_data = section.get_data()
        entropy = calculate_entropy(raw_data)

        # 权限解析
        chars = section.Characteristics
        is_executable = bool(chars & 0x20000000)  # IMAGE_SCN_MEM_EXECUTE
        is_writable = bool(chars & 0x80000000)    # IMAGE_SCN_MEM_WRITE
        is_readable = bool(chars & 0x40000000)    # IMAGE_SCN_MEM_READ

        section_info = {
            'name': name,
            'virtual_address': hex(section.VirtualAddress),
            'virtual_size': section.Misc_VirtualSize,
            'raw_size': section.SizeOfRawData,
            'entropy': round(entropy, 4),
            'executable': is_executable,
            'writable': is_writable,
            'readable': is_readable
        }
        results.append(section_info)

        # 异常检测逻辑
        # 1. 可写且可执行(W^X违规)
        if is_writable and is_executable:
            anomalies.append(f"节区 {name}: 可写且可执行")

        # 2. 高熵值(>7.2 通常表示加壳或加密)
        if entropy > 7.2:
            anomalies.append(f"节区 {name}: 熵值过高 ({entropy:.2f}),疑似加壳/加密")

        # 3. 已知壳特征名称
        packed_names = ['UPX0', 'UPX1', '.aspack', '.adata', '.packed', 'PECompact']
        if name in packed_names:
            anomalies.append(f"节区 {name}: 名称匹配已知壳特征")

    pe.close()
    return results, anomalies

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 2:
        print("用法: python pe_section_analyzer.py <PE文件路径>")
        sys.exit(1)

    sections, anomalies = analyze_sections(sys.argv[1])

    print("=" * 70)
    print(f"{'节区名':<12} {'VA':<10} {'VSize':<10} {'RawSize':<10} {'熵值':<8} {'R':<4} {'W':<4} {'X':<4}")
    print("=" * 70)
    for s in sections:
        print(f"{s['name']:<12} {s['virtual_address']:<10} {s['virtual_size']:<10} {s['raw_size']:<10} "
              f"{s['entropy']:<8.4f} {str(s['readable']):<4} {str(s['writable']):<4} {str(s['executable']):<4}")

    print("\n异常检测结果:")
    if anomalies:
        for a in anomalies:
            print(f"  [!] {a}")
    else:
        print("  未发现明显异常节区")

运行与验证

  1. 保存上述代码为pe_section_analyzer.py
  2. 运行命令:python pe_section_analyzer.py sample.exe
  3. 观察输出表格中每个节区的权限列(R/W/X)和熵值列

判定标准:

  • 若某节区同时显示W=True和X=True,则该节区可写可执行,属于异常
  • 若熵值大于7.2,表明该节区数据接近随机分布,极可能经过加密或压缩(加壳)
  • 若节区名称为UPX0/UPX1等已知壳特征,直接判定为加壳
  • 正常编译的PE文件(如未加壳的C程序)通常熵值在5.0~7.0之间,且不存在W+X同时为真的节区

验证示例:对一个正常的notepad.exe运行此脚本,预期输出中所有节区熵值均低于7.0,且没有任何节区同时具备可写和可执行权限。若分析加壳样本,则会在异常检测部分输出对应的告警信息。

常见问题

  • 熵值计算范围:代码使用section.get_data()获取原始数据,该数据可能包含文件对齐填充字节。若填充字节过多会降低熵值,可在实际分析中改用VirtualSize截断数据,但本案例为保持简洁未做此优化。
  • 权限位定义:代码中使用的三个权限位(0x20000000/0x80000000/0x40000000)是PE规范中标准定义,适用于所有PE文件。
  • 壳特征库:实际分析中应扩展已知壳名称列表,本案例仅列出常见示例用于演示。