
大家好
有关为IDA Pro编写各种有用的东西的一系列文章仍在继续。 上一次我们修复处理器模块时 ,今天我们将集中精力为一种老式操作系统(即AmigaOS )编写一个加载器模块(loader)。 我们将用Python编写。 我还将尝试揭示使用relocations
(它们也是relocations
)时的一些微妙之处,这些relocations
可在许多可执行文件( PE
, ELF
, MS-DOS
等)中找到。
引言
那些以前使用过Amiga Hunk格式(在AmigaOS中,这是包含可执行代码的对象的名称: executable
-, library
文件等)并在IDA中至少加载了一个这样的文件的人,可能会看到引导加载程序是它已经存在(此外, IDA SDK
中甚至还有源):


是的,确实,所有内容都已经写在我们面前了,但是...一切都实现得很差,以至于至少无法使用某些常规的可执行文件来工作。
当前的实施问题
因此,这里列出了一些问题:
- 搬迁。 在Amiga Hunk文件中,它们的存在是正常的做法。 在现有的实现中,它们甚至在下载文件时也适用。 但这并不总是正确完成的(结果链接可能无法正确计算)。
另外,您将无法执行“ Rebase program ... ”。 加载程序中没有此功能。 - 该文件从基地址
0x00000000
下载。 这绝对是错误的,因为各种系统库都以零偏移量加载。 结果,在下载文件的地址空间中创建了到这些库的链接。 - 可以使用各种标志设置装载程序,这些标志不允许(或者相反,允许)IDA执行某些“识别”动作:定义指针,数组,汇编器指令。
在不涉及x86 / x64 / ARM的情况下,通常在下载文件后,汇编列表看起来就像您想一眼就关闭IDA。 (并删除调查的文件并学习如何使用radare2) 。 原因是默认的引导加载程序标志。

编写引导加载程序模板
实际上,编写引导加载程序很容易。 需要实现三个回调:
1) accept_file(li, filename)
通过此功能,IDA确定是否可以使用该加载程序来下载filename
def accept_file(li, filename): li.seek(0) tag = li.read(4) if tag == 'TAG1':
2) load_file(li, neflags, format)
在这里,文件的内容下载到数据库中,创建段/结构/类型,使用重新锁定和其他操作。
def load_file(li, neflags, format):
3) move_segm(frm, to, sz, fileformatname)
如果下载的文件中有重定位,则不能只获取并更改基址。 有必要重新计算所有重定位和补丁链接。 通常,代码将始终相同。 在这里,我们只需经历所有先前创建的重新锁定,向它们添加一个增量,然后将修补程序应用于已下载文件的字节。
def move_segm(frm, to, sz, fileformatname): delta = to xEA = ida_fixup.get_first_fixup_ea() while xEA != idaapi.BADADDR: fd = ida_fixup.fixup_data_t(idaapi.FIXUP_OFF32) ida_fixup.get_fixup(xEA, fd) fd.off += delta if fd.get_type() == ida_fixup.FIXUP_OFF8: idaapi.put_byte(xEA, fd.off) elif fd.get_type() == ida_fixup.FIXUP_OFF16: idaapi.put_word(xEA, fd.off) elif fd.get_type() == ida_fixup.FIXUP_OFF32: idaapi.put_long(xEA, fd.off) fd.set(xEA) xEA = ida_fixup.get_next_fixup_ea(xEA) idaapi.cvar.inf.baseaddr = idaapi.cvar.inf.baseaddr + delta return 1
引导程序模板 import idaapi import ida_idp import ida_fixup def accept_file(li, filename): li.seek(0) tag = li.read(4) if tag == 'TAG1':
编写主引导程序代码
因此,整理出基本知识。 让我们为自己准备一个“工作场所”。 我不知道有人喜欢用Python编写代码,但是我喜欢用PyCharm编写代码。 让我们创建一个新项目,并在路径中添加IDA中的目录以搜索导入:

已经面临为AmigaOS模拟可执行文件的问题的人们可能听说过amitools之类的项目。 它具有与Amiga Hunk一起使用的几乎完整的工具集(用于仿真和解析)。 我建议在此基础上进行“引导加载程序”(项目许可允许,我们的引导加载程序将是非盈利的)。
在对amitools
进行简短搜索amitools
找到amitools
文件。 它实现了文件解析,定义段,重定位等等。 实际上, Relocate.py文件负责使用重新锁定。
现在最难的部分:从所有amitools
文件树中,您amitools
将BinFmtHunk.py
和Relocate.py
引用的所有内容拖到我们的引导加载程序文件中,在某些地方进行了较小的更正。
我要添加的另一件事是从中下载数据的文件中位置的每个段的定义。 这是通过将data_offset
属性添加到两个类来完成的: HunkSegmentBlock
和HunkOverlayBlock
。 原来的代码如下:
HunkSegmentBlock class HunkSegmentBlock(HunkBlock): """HUNK_CODE, HUNK_DATA, HUNK_BSS""" def __init__(self, blk_id=None, data=None, data_offset=0, size_longs=0): HunkBlock.__init__(self) if blk_id is not None: self.blk_id = blk_id self.data = data self.data_offset = data_offset self.size_longs = size_longs def parse(self, f): size = self._read_long(f) self.size_longs = size if self.blk_id != HUNK_BSS: size *= 4 self.data_offset = f.tell() self.data = f.read(size)
HunkOverlayBlock class HunkOverlayBlock(HunkBlock): """HUNK_OVERLAY""" blk_id = HUNK_OVERLAY def __init__(self): HunkBlock.__init__(self) self.data_offset = 0 self.data = None def parse(self, f): num_longs = self._read_long(f) self.data_offset = f.tell() self.data = f.read(num_longs * 4)
现在,我们需要将此属性添加到Segment
类,该类稍后由Hunk
块创建:
区隔 class Segment: def __init__(self, seg_type, size, data=None, data_offset=0, flags=0): self.seg_type = seg_type self.size = size self.data_offset = data_offset self.data = data self.flags = flags self.relocs = {} self.symtab = None self.id = None self.file_data = None self.debug_line = None
接下来,对于BinFmtHunk
类, BinFmtHunk
在创建线段时添加data_offset
。 这是在段块枚举循环中的create_image_from_load_seg_file
方法中完成的:
create_image_from_load_seg_file segs = lsf.get_segments() for seg in segs:
为IDA回调编写代码
现在我们有了所需的一切,让我们为回调编写代码。 第一个将是accept_file
:
accept_file def accept_file(li, filename): li.seek(0) bf = BinFmtHunk() tag = li.read(4) tagf = StringIO.StringIO(tag) if bf.is_image_fobj(tagf): return {'format': 'Amiga Hunk executable', 'processor': '68040'} else: return 0
一切都很简单:我们读取前四个字节,从中创建一个虚拟文件( StringIO
),然后将其传递给is_image_fobj
函数,如果文件格式True
,该函数将返回True
。 在这种情况下,我们返回具有两个字段的字典: format
(已加载格式的文本说明)和processor
(在哪个平台上编写可执行代码)。
接下来,您需要将文件上传到IDB。 更复杂。 首先要做的是将处理器类型强制为所需的Motorola 68040
:
idaapi.set_processor_type('68040', ida_idp.SETPROC_LOADER)
设置引导加载程序的标志,以便不会识别任何游戏,并且不会从一行中的所有内容组成数组(可以在此处找到标志的描述):
idaapi.cvar.inf.af = idaapi.AF_CODE | idaapi.AF_JUMPTBL | idaapi.AF_USED | idaapi.AF_UNK | \ idaapi.AF_PROC | idaapi.AF_LVAR | idaapi.AF_STKARG | idaapi.AF_REGARG | \ idaapi.AF_TRACE | idaapi.AF_VERSP | idaapi.AF_ANORET | idaapi.AF_MEMFUNC | \ idaapi.AF_TRFUNC | idaapi.AF_FIXUP | idaapi.AF_JFUNC | idaapi.AF_NULLSUB | \ idaapi.AF_NULLSUB | idaapi.AF_IMMOFF | idaapi.AF_STRLIT
我们将下载文件的内容传输到BinFmtHunk
(进行解析):
li.seek(0) data = li.read(li.size()) bf = BinFmtHunk() fobj = StringIO.StringIO(data) bi = bf.load_image_fobj(fobj)
对于零下载地址,我建议选择另一个ImageBase
。 顺便说一下,AmigaOS中的可执行文件仅在可访问的地址下载,那里没有虚拟地址。 我选择了0x21F000
,它很漂亮,并且不太可能与任何常量一致。 应用它:
rel = Relocate(bi)
添加程序从其开始的起始地址:
现在是时候将段加载到数据库并创建重新锁定了(使用IDA术语: reloc == fixup
):
for seg in bi.get_segments(): offset = addrs[seg.id] size = seg.size to_segs = seg.get_reloc_to_segs() for to_seg in to_segs: reloc = seg.get_reloc(to_seg) for r in reloc.get_relocs(): offset2 = r.get_offset() rel_off = Relocate.read_long(datas[seg.id], offset2) addr = offset + rel_off + r.addend fd = idaapi.fixup_data_t(idaapi.FIXUP_OFF32) fd.off = addr fd.set(offset + offset2) idaapi.mem2base(str(datas[seg.id]), offset, seg.data_offset) idaapi.add_segm(0, offset, offset + size, 'SEG_%02d' % seg.id, seg.get_type_name())
最终结果为load_file
:
return 1
代码move_segm
只需更改即可。
引导加载程序摘要代码和结论
amiga_hunk.py import idaapi import ida_idp import ida_fixup import StringIO import struct HUNK_UNIT = 999 HUNK_NAME = 1000 HUNK_CODE = 1001 HUNK_DATA = 1002 HUNK_BSS = 1003 HUNK_ABSRELOC32 = 1004 HUNK_RELRELOC16 = 1005 HUNK_RELRELOC8 = 1006 HUNK_EXT = 1007 HUNK_SYMBOL = 1008 HUNK_DEBUG = 1009 HUNK_END = 1010 HUNK_HEADER = 1011 HUNK_OVERLAY = 1013 HUNK_BREAK = 1014 HUNK_DREL32 = 1015 HUNK_DREL16 = 1016 HUNK_DREL8 = 1017 HUNK_LIB = 1018 HUNK_INDEX = 1019 HUNK_RELOC32SHORT = 1020 HUNK_RELRELOC32 = 1021 HUNK_ABSRELOC16 = 1022 HUNK_PPC_CODE = 1257 HUNK_RELRELOC26 = 1260 hunk_names = { HUNK_UNIT: "HUNK_UNIT", HUNK_NAME: "HUNK_NAME", HUNK_CODE: "HUNK_CODE", HUNK_DATA: "HUNK_DATA", HUNK_BSS: "HUNK_BSS", HUNK_ABSRELOC32: "HUNK_ABSRELOC32", HUNK_RELRELOC16: "HUNK_RELRELOC16", HUNK_RELRELOC8: "HUNK_RELRELOC8", HUNK_EXT: "HUNK_EXT", HUNK_SYMBOL: "HUNK_SYMBOL", HUNK_DEBUG: "HUNK_DEBUG", HUNK_END: "HUNK_END", HUNK_HEADER: "HUNK_HEADER", HUNK_OVERLAY: "HUNK_OVERLAY", HUNK_BREAK: "HUNK_BREAK", HUNK_DREL32: "HUNK_DREL32", HUNK_DREL16: "HUNK_DREL16", HUNK_DREL8: "HUNK_DREL8", HUNK_LIB: "HUNK_LIB", HUNK_INDEX: "HUNK_INDEX", HUNK_RELOC32SHORT: "HUNK_RELOC32SHORT", HUNK_RELRELOC32: "HUNK_RELRELOC32", HUNK_ABSRELOC16: "HUNK_ABSRELOC16", HUNK_PPC_CODE: "HUNK_PPC_CODE", HUNK_RELRELOC26: "HUNK_RELRELOC26", } EXT_SYMB = 0 EXT_DEF = 1 EXT_ABS = 2 EXT_RES = 3 EXT_ABSREF32 = 129 EXT_ABSCOMMON = 130 EXT_RELREF16 = 131 EXT_RELREF8 = 132 EXT_DEXT32 = 133 EXT_DEXT16 = 134 EXT_DEXT8 = 135 EXT_RELREF32 = 136 EXT_RELCOMMON = 137 EXT_ABSREF16 = 138 EXT_ABSREF8 = 139 EXT_RELREF26 = 229 TYPE_UNKNOWN = 0 TYPE_LOADSEG = 1 TYPE_UNIT = 2 TYPE_LIB = 3 HUNK_TYPE_MASK = 0xffff SEGMENT_TYPE_CODE = 0 SEGMENT_TYPE_DATA = 1 SEGMENT_TYPE_BSS = 2 BIN_IMAGE_TYPE_HUNK = 0 segment_type_names = [ "CODE", "DATA", "BSS" ] loadseg_valid_begin_hunks = [ HUNK_CODE, HUNK_DATA, HUNK_BSS, HUNK_PPC_CODE ] loadseg_valid_extra_hunks = [ HUNK_ABSRELOC32, HUNK_RELOC32SHORT, HUNK_DEBUG, HUNK_SYMBOL, HUNK_NAME ] class HunkParseError(Exception): def __init__(self, msg): self.msg = msg def __str__(self): return self.msg class HunkBlock: """Base class for all hunk block types""" def __init__(self): pass blk_id = 0xdeadbeef sub_offset = None
: https://github.com/lab313ru/amiga_hunk_loader
IDA, , . «» Python , , ( -?), .
- IDA , . , - , , .
.