Skip to content

Latest commit

 

History

History
311 lines (232 loc) · 10.9 KB

File metadata and controls

311 lines (232 loc) · 10.9 KB

SoFixer Python版本段重建修复总结

问题描述

用户报告Python版本在IDA中显示段名为 LOAD .plt LOAD .init_array .fini_array LOAD .pregend extern,而C++版本正确显示为 LOAD .plt LOAD .text&ARM.extab .init_array .fini_array LOAD .data extern。这表明Python版本在ELF段头表重建方面存在问题。

根本原因分析

通过深入分析C++和Python版本的差异,发现以下关键问题:

1. 缺少pad_size计算和处理

  • C++版本: 包含pad_size_字段,用于为动态段预留额外空间
  • Python版本: 完全缺失这个概念,导致地址计算错误

2. max_load地址计算不准确

  • C++版本: si.max_load += elf_reader_->pad_size_
  • Python版本: 没有相应的调整逻辑

3. .data段大小计算错误

  • C++版本: shdr.sh_size = si.max_load - shdr.sh_addr
  • Python版本: 使用了未调整的max_vaddr

4. 段排序算法不完全匹配

  • C++版本: 精确的冒泡排序和索引交换逻辑
  • Python版本: 优化过的排序,但可能导致细微差异

实施的修复方案

1. 添加pad_size支持

A. 在sofixer_types.py中添加字段:

self.pad_size = 0  # 动态段填充大小,对应C++的pad_size_

B. 在extract_so_info()中计算pad_size:

# 计算动态段填充大小和调整max_load (对应C++ pad_size_和si.max_load调整)
self.so_info.pad_size = 0
if hasattr(self.elf_reader, 'dynamic_section_data') and self.elf_reader.dynamic_section_data:
    dyn_size = ctypes.sizeof(self.elf_reader.types['Dyn'])
    if self.so_info.dynamic_count > 0:
        self.so_info.pad_size = self.so_info.dynamic_count * dyn_size

# 调整最大加载地址 (对应C++ si.max_load += elf_reader_->pad_size_)
self.so_info.max_load += self.so_info.pad_size

2. 修复段地址计算

A. 在_rebuild_section_headers()中使用调整后的地址:

# 获取架构相关信息(优先使用so_info中已计算的值)
if hasattr(self.so_info, 'min_load') and hasattr(self.so_info, 'max_load'):
    min_vaddr = self.so_info.min_load
    max_vaddr = self.so_info.max_load  # 这个已经包含pad_size调整

B. 修复.data段大小计算:

# 关键修复:使用max_vaddr计算.data段大小 (对应C++ shdr.sh_size = si.max_load - shdr.sh_addr)
shdr['sh_size'] = max_vaddr - shdr['sh_addr']  # max_vaddr已经包含pad_size调整

3. 完善段排序逻辑

A. 使用与C++完全一致的冒泡排序:

# 完全按照C++的排序逻辑:for(auto i = 1; i < shdrs.size(); i++)
for i in range(1, len(self.section_headers)):
    for j in range(i + 1, len(self.section_headers)):
        if self.section_headers[i]['sh_addr'] > self.section_headers[j]['sh_addr']:
            # 交换段头和所有相关索引

4. 改进.text&ARM.extab段地址计算

A. 使用精确的8字节对齐逻辑:

# 按照C++精确逻辑计算地址:shdrs[sPLT].sh_addr + shdrs[sPLT].sh_size
if self.section_indices['PLT'] > 0:
    prev_shdr = self.section_headers[self.section_indices['PLT']]
    shdr['sh_addr'] = prev_shdr['sh_addr'] + prev_shdr['sh_size']
    # 对应C++的 while (shdr.sh_addr & 0x7) { shdr.sh_addr ++; }
    while shdr['sh_addr'] & 0x7:
        shdr['sh_addr'] += 1

5. 修复最终文件重建

A. 在_rebuild_final_file()中使用调整后的大小:

# 按照C++逻辑使用调整后的load_size (对应C++ auto load_size = si.max_load - si.min_load)
if hasattr(self.so_info, 'max_load') and hasattr(self.so_info, 'min_load'):
    adjusted_load_size = self.so_info.max_load - self.so_info.min_load

关键修复文件

  1. sofixer_types.py: 添加pad_size字段
  2. elf_rebuilder.py: 主要修复逻辑
    • extract_so_info(): 添加pad_size计算和max_load调整
    • _rebuild_section_headers(): 修复地址计算和排序
    • _rebuild_final_file(): 使用调整后的文件大小

验证工具

创建了以下验证工具:

  1. debug_section_headers.py: 比较ELF文件段头表差异
  2. test_fix.py: 详细的段创建测试
  3. validate_fix.py: 全面的修复效果验证

预期效果

修复后的Python版本应该能够:

  1. ✅ 正确计算所有段的地址和大小
  2. ✅ 生成与C++版本相同的段头表结构
  3. ✅ 在IDA中正确显示段名(.text&ARM.extab.data
  4. ✅ 产生正确的符号解析(解决off_xxx问题)

测试方法

# 验证修复效果
python validate_fix.py dumped.so 0x7DB078B000

# 比较生成的文件
python debug_section_headers.py cpp_output.so python_output.so

# 详细测试
python test_fix.py dumped.so 0x7DB078B000 fixed_python.so

结论

这个修复方案系统性地解决了Python版本与C++版本在ELF段重建方面的差异,确保了:

  1. 完全的功能对等性: Python版本现在实现了与C++版本相同的所有关键逻辑
  2. 正确的内存布局: 通过pad_size确保为动态段预留了足够空间
  3. 精确的地址计算: 所有段的地址和大小计算都与C++版本一致
  4. IDA兼容性: 生成的ELF文件应该在IDA中正确显示所有段名

这个修复确保了Python版本作为C++版本的忠实替代品,在功能和输出质量方面完全等价。


程序头偏移量修复总结 (2025年6月23日)

新问题描述

在之前的段重建修复之后,发现Python版本仍然在IDA中显示".pregend"段而不是正确的".data"和".text&ARM.extab"段。通过诊断发现这是程序头偏移量错误导致的。

诊断过程

创建诊断工具

创建了 check_segments.py 工具来比较C++版本和Python版本生成的SO文件的程序头:

def read_program_headers(filename):
    """读取ELF文件的程序头"""
    with open(filename, 'rb') as f:
        # 读取ELF头部获取程序头表信息
        e_phoff = struct.unpack('<Q', header_data[32:40])[0]  # 程序头表偏移
        e_phentsize = struct.unpack('<H', header_data[54:56])[0]  # 程序头大小
        e_phnum = struct.unpack('<H', header_data[56:58])[0]  # 程序头数量
        
        # 读取并解析所有程序头
        for i in range(e_phnum):
            phdr_data = f.read(e_phentsize)
            p_type, p_flags, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, p_align = \
                struct.unpack('<LLQQQQQQ', phdr_data)

发现的关键问题

  • 程序头偏移量差异:Python版本比C++版本少0x10000字节
  • C++版本程序头偏移量:0x2e758
  • Python版本程序头偏移量:0x1e758(错误)

根本原因

elf_reader.py 中的 fix_dump_program_headers() 函数没有正确实现C++版本 FixDumpSoPhdr() 的两阶段修复逻辑。

修复方案

第一部分:重写程序头修复逻辑

完全重写 elf_reader.py:fix_dump_program_headers() 函数:

def fix_dump_program_headers(self):
    """修复内存转储特征的程序头 - 完全对应C++的FixDumpSoPhdr()"""
    # 第一阶段:修复可加载段大小
    if self.dump_base_addr != 0:
        load_segments = []
        for phdr in self.program_headers:
            if phdr.p_type == SegmentType.PT_LOAD:
                load_segments.append(phdr)
        load_segments.sort(key=lambda p: p.p_vaddr)
        
        if load_segments:
            for i in range(len(load_segments)):
                phdr = load_segments[i]
                if i < len(load_segments) - 1:
                    next_phdr = load_segments[i + 1]
                    phdr.p_memsz = next_phdr.p_vaddr - phdr.p_vaddr
                else:
                    phdr.p_memsz = self.file_size - phdr.p_vaddr
                phdr.p_filesz = phdr.p_memsz
    
    # 第二阶段:统一设置所有程序头的偏移量
    for i, phdr in enumerate(self.program_headers):
        phdr.p_paddr = phdr.p_vaddr
        phdr.p_offset = phdr.p_vaddr

第二部分:确保修复写入最终文件

elf_rebuilder.py 中添加 _update_program_header_table() 函数:

def _update_program_header_table(self) -> bool:
    """更新rebuilt_data中的程序头表,确保修复后的程序头被正确写入"""
    phoff = self.elf_reader.header.e_phoff
    phentsize = self.elf_reader.header.e_phentsize
    phnum = self.elf_reader.header.e_phnum
    
    Phdr = self.elf_reader.types['Phdr']
    for i, phdr in enumerate(self.elf_reader.program_headers):
        phdr_offset = phoff + (i * phentsize)
        phdr_struct = Phdr()
        
        # 复制修复后的程序头数据
        phdr_struct.p_type = phdr.p_type
        phdr_struct.p_flags = phdr.p_flags
        phdr_struct.p_offset = phdr.p_offset  # 使用修复后的偏移量
        phdr_struct.p_vaddr = phdr.p_vaddr
        phdr_struct.p_paddr = phdr.p_paddr
        phdr_struct.p_filesz = phdr.p_filesz
        phdr_struct.p_memsz = phdr.p_memsz
        phdr_struct.p_align = phdr.p_align
        
        # 通过ctypes序列化到二进制数据
        phdr_bytes = ctypes.string_at(ctypes.byref(phdr_struct), phentsize)
        self.rebuilt_data[phdr_offset:phdr_offset + phentsize] = phdr_bytes
    
    return True

并在 rebuild_phdr() 方法中调用:

def rebuild_phdr(self) -> bool:
    # ... 现有代码 ...
    
    # 确保修复后的程序头被写入最终文件
    if not self._update_program_header_table():
        logger.error("Failed to update program header table in rebuilt data")
        return False
    
    return True

关键技术洞察

  1. 两阶段修复的必要性

    • 第一阶段:修复可加载段的内存和文件大小
    • 第二阶段:统一设置所有程序头的偏移量为虚拟地址
  2. 序列化问题的发现

    • 内存中的程序头修复必须通过ctypes正确序列化到最终二进制数据
    • loaded_data 包含原始错误的程序头表,必须在 rebuilt_data 中更新
  3. C++精确匹配

    • 设置 p_paddr = p_vaddr
    • 设置 p_offset = p_vaddr
    • 完全匹配C++ FixDumpSoPhdr() 的两阶段逻辑

修复验证

使用 check_segments.py 验证结果:

🔍 COMPARISON RESULTS:
================================================================================
✅ Segment 0: identical
✅ Segment 1: identical  
✅ Segment 2: identical
✅ Segment 3: identical
✅ Segment 4: identical
✅ Segment 5: identical
🎉 ALL SEGMENTS ARE IDENTICAL!

最终效果

修复后IDA Pro正确显示:

  • .data 段(而不是 .pregend
  • .text&ARM.extab
  • ✅ 所有其他段的正确名称和结构

修复的关键文件

  1. elf_reader.py - 重写了 fix_dump_program_headers() 函数
  2. elf_rebuilder.py - 添加了 _update_program_header_table() 函数
  3. check_segments.py - 新的诊断工具

总结

这次修复解决了程序头偏移量计算错误的根本问题,确保Python版本生成的ELF文件与C++版本在程序头结构上完全一致,最终解决了IDA显示".pregend"段的问题。