def parse(self): if ida_segment.get_segm_by_name('DYLD_CACHE_HEADER'): seg = ida_segment.get_first_seg() def handle(seg): name = ida_segment.get_segm_name(seg) try: mod, segname = name.split(':') except ValueError: return if segname == '__objc_protolist': self.handle_proto_seg(seg) elif segname == '__objc_classlist': self.handle_class_seg(seg) while seg: handle(seg) seg = ida_segment.get_next_seg(seg.start_ea) return protocols = ida_segment.get_segm_by_name('__objc_protolist') if protocols: self.handle_proto_seg(protocols) classes = ida_segment.get_segm_by_name('__objc_classlist') if classes: self.handle_class_seg(classes)
def build(): seg = ida_segment.get_segm_by_name('__text') for f in idautils.Functions(start=seg.startEA, end=seg.endEA): name = idc.GetFunctionName(f) DB.shared.add_node(f, name) if name[0] in ['-', '+']: MachO.oc_methods.append(f) elif name.startswith('sub_'): MachO.subroutines.append(f) func_type = MachO.get_func_type(f) # 根据IDA提供的func prototype(可能会失败) if func_type: MachO.func_data_frm_ida[f] = func_type if f in MachO.oc_methods: cls, sel = name[1:].strip('[]').split() rec = func_type[0].type.__str__() if rec not in MachO.funcs_index_by_rec: MachO.funcs_index_by_rec[rec] = {sel: f} else: MachO.funcs_index_by_rec[rec][sel] = f if sel not in MachO.funcs_index_by_sel: MachO.funcs_index_by_sel[sel] = {rec: f} else: MachO.funcs_index_by_sel[sel][rec] = f if len(func_type) == 2: MachO.func_has_no_args.append(f) ret_type = func_type.rettype.__str__() if ret_type == 'id' and cls in OCClass.pool_indexed_by_name: # TODO category if not OCClass.pool_indexed_by_name[cls].is_method_a_getter(sel, f): MachO.funcs_ret_id.append(f)
def main(): """ for each function in .text segment, we extract name, start_ea, end_ea, code, size attributes from IDA """ results = {} start = time.time() funcs = get_funcs() text_segm = ida_segment.get_segm_by_name(".text") for func in funcs: # we only care about functions in .text segment if not in_range(text_segm, func.start_ea): continue name = ida_funcs.get_func_name(func.start_ea) code = ida_hexrays.decompile_func(func, None) results[name] = { "name": name, "start_ea": func.start_ea, "end_ea": func.end_ea, "code": code.__str__(), "size": func.size() } print("JSON:") print(json.dumps({"time": time.time() - start, "data": results}))
def fill_mem(self, op, dtype, addr): op.type = ida_ua.o_mem op.dtype = dtype #op.addr = addr # add data segment base addr ds = ida_segment.get_segm_by_name('VM_DATA') op.addr = ds.start_ea + addr
def _get_seg(possible_seg_names): for seg_name in possible_seg_names: seg = ida_segment.get_segm_by_name(seg_name) if seg: return seg return None
def build_from_IDA(): seg = ida_segment.get_segm_by_name('__objc_protolist') for ea in range(seg.startEA, seg.endEA, 8): p = Protocol(ida_bytes.get_qword(ea)) p.extract_info_from_IDA() Protocol.pool_indexed_by_ea[p.ea] = p Protocol.pool_indexed_by_name[p.name] = Protocol.pool_indexed_by_name[p.name] + [ p, ] if p.name in Protocol.pool_indexed_by_name else [p, ]
def SegByName(n): t = ida_segment.get_segm_by_name(n) if (t.start_ea != ida_idaapi.BADADDR): start = t.start_ea end = t.end_ea else: start = ida_idaapi.BADADDR end = ida_idaapi.BADADDR return (start,end)
def build_from_IDA(): seg = ida_segment.get_segm_by_name('__objc_classlist') for class_ea in range(seg.startEA, seg.endEA, 8): objc_data = ida_bytes.get_qword(class_ea) cls = OCClass(objc_data) cls.extract_info_from_IDA() OCClass.pool_indexed_by_name[cls.name] = cls if cls.name not in OCClass.pool_indexed_by_name else \ OCClass.pool_indexed_by_name[cls.name] OCClass.pool_indexed_by_ea[cls.ea] = cls
def find_mclib_jumper(): """Retrieves the address of the McLib jumper fills by the loader during the trustlet loading process :return: McLib jumper address :rtype: int """ segm = ida_segment.get_segm_by_name("rtm") mclib_jumper = ida_bytes.get_dword(segm.start_ea + 0x8C) return mclib_jumper
def find_in_segment(self, value, seg_name_or_ea): """ Searches memory for given value within the range of a specific segment. :param bytes value: byte string to search for :param seg_name_or_ea: segment name or address within segment. :return int: address where value was located or -1 if not found """ if isinstance(seg_name_or_ea, str): segment = ida_segment.get_segm_by_name(seg_name_or_ea) else: segment = ida_segment.getseg(seg_name_or_ea) return self.find(value, start=segment.start_ea, end=segment.end_ea)
def iter_dynamic_functions() -> Iterable[Tuple[int, str]]: """ Iterates the dynamically resolved function signatures. :yield: (ea, name) """ # Look for data elements in the .data segment in which IDA has placed # a function signature element on. data_segment = ida_segment.get_segm_by_name(".data") for ea in idautils.Heads(start=data_segment.start_ea, end=data_segment.end_ea): flags = ida_bytes.get_flags(ea) if idc.is_data(flags) and not idc.is_strlit(flags) and _is_func_type( ea): yield ea, ida_name.get_name(ea)
def _get_segments(self, segname=None): """ Obtain the bytes of the segment specified in segname or all segments as an iterable. :param str segname: segment name or None :yield: seg_start, seg_bytes """ if segname: seg_starts = [ida_segment.get_segm_by_name(segname).start_ea] else: seg_starts = idautils.Segments() for ea in seg_starts: yield ea, segments.get_bytes(ea)
def get_start(name_or_addr): """ Retrieves the starting address for the segment containing the given name or address. :param string|int name_or_addr: either the name of a segment or an EA within a segment """ if isinstance(name_or_addr, str): segment = ida_segment.get_segm_by_name(name_or_addr) if segment is None: raise AssertionError( "could not find segment for {}".format(name_or_addr)) return segment.start_ea elif isinstance(name_or_addr, numbers.Number): return idc.get_segm_attr(name_or_addr, idc.SEGATTR_START) else: raise ValueError("Invalid value: {}".format(name_or_addr))
def main(argv=None): if argv is None: argv = sys.argv[:] try: seg_spec = prompt_for_segment() except BadInputError: logger.error('bad input, exiting...') return -1 seg = ida_segment.get_segm_by_name(seg_spec.name) if not seg: logger.error("bad segment, exiting...") buf = ida_bytes.get_bytes(seg.start_ea, seg.end_ea - seg.start_ea) with open(seg_spec.path, "wb") as f: f.write(buf) logger.info("wrote %x bytes", len(buf))
from idaapi import get_bytes, get_inf_structure from ida_name import set_name from ida_segment import get_segm_by_name from find_kallsyms import find_kallsyms_in_rodata rodata_segm = get_segm_by_name('.rodata') rodata_size = rodata_segm.end_ea - rodata_segm.start_ea + 1 rodata = bytearray(get_bytes(rodata_segm.start_ea, rodata_size)) inf = get_inf_structure() endianness = '>' if inf.is_be() else '<' for address, name in find_kallsyms_in_rodata(rodata, endianness): if name[0] != 'A': set_name(address, str(name[1:]))
for name, (addr, size) in data["peripherals"].items(): seg = ida_segment.getseg(addr) if seg: old_name = ida_segment.get_segm_name(seg) ida_segment.set_segm_name(seg, "%s_%s" % (old_name, name)) else: add_segment(addr, size, name) for name, (addr, reg_count, reg_size, clu_count, clu_size) in data["addresses"].items(): for m in range(clu_count): for n in range(reg_count): reg_name = name.replace('<m>', str(m)).replace('<n>', str(n)) reg_addr = addr + m * clu_size + n * reg_size ida_bytes.create_data(reg_addr, { 1: ida_bytes.byte_flag(), 2: ida_bytes.word_flag(), 4: ida_bytes.dword_flag() }[reg_size], reg_size, ida_netnode.BADNODE) ida_name.set_name(reg_addr, reg_name) base_addr = ida_segment.get_segm_by_name("ROM").start_ea for name, offset in data["interrupts"].items(): addr = base_addr + (16 + offset) * 4 name = "%s_%s" % ("arm" if offset < 0 else "irq", name.lower()) ida_bytes.del_items(addr, 0, 4) ida_bytes.create_dword(addr, 4, True) ida_name.set_name(addr, name, 0) if ida_bytes.get_dword(addr) > 0: ida_offset.op_plain_offset(addr, 0, 0)
def dump_segment(segm_name, from_debugger=True): segm = ida_segment.get_segm_by_name(segm_name) data_length = segm.end_ea - segm.start_ea return ida_bytes.get_bytes(segm.start_ea, data_length, from_debugger)
#Attempts to find & name callbacks registered with REGISTERCLASS import ida_funcs import ida_idaapi import ida_name import ida_offset import ida_segment import ida_ua import ida_xref res = ida_name.get_name_value(ida_idaapi.BADADDR, "REGISTERCLASS") code_ea = ida_segment.get_segm_by_name("_TEXT").start_ea procs = { 0x18: "Create", 0x1C: "Destroy", 0x20: "Icon", 0x24: "Paint", 0x28: "Size", 0x2C: "Input", 0x30: "Focus", 0x34: "Scroll", 0x38: "Data", 0x3C: "Help" } if res[0] == 1: regfunc = res[1] regcall = ida_xref.get_first_cref_to(regfunc) while regcall != ida_idaapi.BADADDR: insn = ida_ua.insn_t() ida_ua.decode_insn(insn, regcall)
import os import ida_segment import ida_kernwin import ida_name filename = ida_kernwin.ask_file(0, "", "FILTER *.MAP\nSelect MAP file") if filename is not None and os.path.isfile(filename): real_base = {} map_base = {} real_base['DGROUP'] = ida_segment.get_segm_by_name("DATA").start_ea real_base['IGROUP'] = ida_segment.get_segm_by_name("TEXT").start_ea with open(filename, "r") as f: data = f.read().split('\n') insection = None for line in data: if line.strip() == 'Origin Group': insection = "groups" elif line.strip() == 'Address Publics by Value': insection = "publics_" elif line.strip() == '' and insection == "publics_": insection = "publics" elif line.strip() == '': insection = None else: if insection == "groups": (addr, name) = line.strip().split(' ')
ROOT_PATH = os.path.abspath(os.path.dirname(__file__)) log_file = open(os.path.join(ROOT_PATH, "./output.log"), "w") binary_info = idaapi.get_inf_structure() if binary_info.is_64bit(): BITS = 64 elif binary_info.is_32bit(): BITS = 32 else: BITS = 16 ENDIAN = "big" if binary_info.is_be() else "little" ARCH = binary_info.procname text_seg = ida_segment.get_segm_by_name(".text") if not text_seg: log_file.write("<%s>: text_seg is not exists!!!!\n" % get_input_file_path()) # text_seg = ida_segment.get_segm_by_name("LOAD") if ARCH == "ARM" and ENDIAN == "little" and BITS == 32: # Identify up to four parameters ARGU_REGS = ['R0', 'R1', 'R2', 'R3', 'ENDs'] JMP_INSTRUCTION = [ 'B', 'BLT', 'BLE', 'BEQ', 'BVC', 'BVS', 'BLS', 'BX', 'BLO', 'BCS', 'BCC', 'BMI', 'BPL', 'BGT', 'BGE', 'BNE', 'BHI', 'BHS' ] RETURN_REG = 0 # R0 elif ARCH == "metapc" and ENDIAN == "little" and BITS == 32: RETURN_REG = 0 # eax
from idaapi import get_bytes, require from ida_segment import get_segm_by_name require('find_kallsyms') require('ida_utils') rodata_segm = get_segm_by_name('.rodata') if rodata_segm is None: rodata_segm = get_segm_by_name('.text') rodata_size = rodata_segm.end_ea - rodata_segm.start_ea + 1 rodata = b''.join(get_bytes(rodata_segm.start_ea, rodata_size)) kallsyms = find_kallsyms.find_kallsyms_in_rodata(rodata) # noqa: F821 ida_utils.apply_kallsyms(kallsyms) # noqa: F821
class FfxivSigmaker: DATASTORE: ClientStructsData GENERATED_DATASTORE: GeneratedData STANDARD_IMAGE_BASE = 0x1_4000_0000 # The ".text" segment, only look for xrefs within this range TEXT_SEGMENT: ida_segment.segment_t = ida_segment.get_segm_by_name(".text") # Max number of XREFs to search through for each address XREFS_TO_SEARCH = 10 # Sigs that are not unique, cache having to search short sigs multiple times _NOT_UNIQUE_SIGNATURES: Set[str] = set() def __init__(self): data_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data.yml") with Path(data_path).open('r') as fd: data_dict = yaml.load(fd) self.DATASTORE = dacite.from_dict(ClientStructsData, data_dict) self._rebase_datastore() generated_data_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), "generated_data.yml") with Path(generated_data_path).open('r') as fd: data_dict = yaml.load(fd) self.GENERATED_DATASTORE = data_dict #dacite.from_dict(GeneratedData, data_dict) def run(self) -> None: """ Run the sigmaker :return: None """ Log.debug('generating global sigs') total_globals = len(self.DATASTORE.globals) for (i, (global_ea, global_name)) in enumerate(self.DATASTORE.globals.items()): status = f'{i}/{total_globals} ({i / total_globals:0.2%})' if global_name in self.GENERATED_DATASTORE.global_sigs: existing_sig = self.GENERATED_DATASTORE.global_sigs[ global_name] if existing_sig != "None": Log.debug( f'{status} // {global_name} // {global_ea:X} // has existing sig: {existing_sig}' ) else: Log.debug( f'{status} // {global_name} // {global_ea:X} // failed sig generation on previous run' ) continue sig = self._sig_address(int(global_ea), False) Log.debug(f'{status} // {global_name} // {global_ea:X} // {sig}') if sig: self.GENERATED_DATASTORE.global_sigs[global_name] = sig else: self.GENERATED_DATASTORE.global_sigs[global_name] = "None" Log.debug('generating class sigs') class_data: GameClass total = len(self.DATASTORE.classes) for (i, (class_name, class_data)) in enumerate(self.DATASTORE.classes.items()): # skip stubs if not class_data: continue status = f'{i}/{total} ({i / total:0.2%})' for (func_ea, func_name) in class_data.funcs.items(): existing_sig = self.GENERATED_DATASTORE.get_function_signature( class_name, func_name) if existing_sig is not None: if existing_sig == "None": Log.debug( f'{status} // {class_name}::{func_name} // {func_ea:X} // failed sig generation on previous run' ) else: Log.debug( f'{status} // {class_name}::{func_name} // {func_ea:X} // has existing sig: {existing_sig}' ) continue sig = self._sig_address(int(func_ea), True) Log.debug( f'{status} // {class_name}::{func_name} // {func_ea:X} // {sig}' ) if sig: self.GENERATED_DATASTORE.set_function_signature( class_name, func_name, sig) else: self.GENERATED_DATASTORE.set_function_signature( class_name, func_name, "None") # global_ea = class_data.g_pointer # if global_ea: # sig = self._sig_address(global_ea, False) # Log.debug(f'{status} // {class_name}::gPointer // {global_ea:X} // {sig}') # if sig: # class_data.g_pointer_sig = sig # # global_ea = class_data.g_instance # if global_ea: # sig = self._sig_address(global_ea, False) # Log.debug(f'{status} // {class_name}::gInstance // {global_ea:X} // {sig}') # if sig: # class_data.g_instance_sig = sig Log.debug(f'{total}/{total} (100.00%)') def export(self) -> None: """ Export generated data to json :return: None """ data_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "generated_data.yml") with Path(data_path).open('w') as fd: yaml.dump(self.GENERATED_DATASTORE, fd) # region Rebasing def _rebase_datastore(self, undo: bool = False) -> None: """ Rebase all the addresses in the datastore. :param undo: Revert a rebasing instead :return: None """ current_image_base = idaapi.get_imagebase() if self.STANDARD_IMAGE_BASE == current_image_base: return rebase_offset = current_image_base - self.STANDARD_IMAGE_BASE if undo: rebase_offset *= -1 self.__rebase_dict(self.DATASTORE.globals, rebase_offset) self.__rebase_dict(self.DATASTORE.functions, rebase_offset) class_data: GameClass for (class_name, class_data) in self.DATASTORE.classes.items(): if not class_data: continue self.__rebase_dict(class_data.funcs, rebase_offset) for vtbl in class_data.vtbls: vtbl.ea += rebase_offset if class_data.g_pointer: class_data.g_pointer += rebase_offset if class_data.g_instance: class_data.g_instance += rebase_offset def __rebase_dict(self, mapping: Dict[int, str], rebase_offset: int) -> None: """ Rebase all addresses in a [addr, _] mapping :param mapping: Mapping to rebase :param rebase_offset: Rebase offset :return: None """ for (addr, name) in list(mapping.items()): mapping[addr + rebase_offset] = mapping.pop(addr) # endregion # region Signature creation def _sig_address(self, addr: int, is_func: bool) -> Optional[str]: """ Find the best sig available for a given address :param addr: Address to sig the xrefs of :param is_func: Address is a func start :return: A signature, or None """ sigs = self.__get_xref_sigs(addr, is_func) sig = self.__find_best_sig(sigs) return sig def __get_xref_sigs(self, addr: int, is_func: bool) -> Iterator[str]: """ Create a signature from a function address XRef sigs are preferred, however if none are available the func itself will be used :param addr: Function address :param is_func: Indicates that this address is a func, and can be signatured directly :return: A series of Dalamud compatible signatures """ xref_addrs = [xref.frm for xref in idautils.XrefsTo(addr)] if is_func and not ida_funcs.get_func(addr): Log.warn( f'Address at {addr:X} is identified as a func, but is not in IDA, attempting to make a subroutine' ) ida_funcs.add_func(addr) # This should prune xrefs in places like .pdata by only keeping xrefs in a function xref_addrs = list(filter(ida_funcs.get_func_name, xref_addrs)) # Grab the first N xrefs xref_addrs = xref_addrs[:self.XREFS_TO_SEARCH] if is_func: # Try to sig the func itself as well xref_addrs.insert(0, addr) for xref_addr in xref_addrs: yield from SigGen(xref_addr) def __find_best_sig(self, sigs: Iterator[str]) -> Optional[str]: """ Find the shortest signature, with only one match in the .TEXT segment. :param sigs: Signatures to check :return: The shortest signature, or None """ chosen_sig = None chosen_sig_size = sys.maxsize for sig in sigs: # Already have a sig shorter than this one if len(sig) >= chosen_sig_size: continue if sig in self._NOT_UNIQUE_SIGNATURES: continue if self.__is_sig_unique(sig): chosen_sig = sig chosen_sig_size = len(sig) else: self._NOT_UNIQUE_SIGNATURES.add(sig) return chosen_sig def __is_sig_unique(self, sig: str) -> bool: """ Perform a binary sig search and determine if the signature has one unique match :param sig: Sig to search :return: True if only one address matches """ # Expects a byte array fmt_sig = [ int(s, 16).to_bytes(1, 'little') if s != '??' else b'\0' for s in sig.split(' ') ] fmt_sig = b''.join(fmt_sig) # Another byte array, 0 = "??" wildcard sig_mask = [ int(b != '??').to_bytes(1, 'little') for b in sig.split(' ') ] sig_mask = b''.join(sig_mask) result_count = 0 sig_addr = self.TEXT_SEGMENT.start_ea # noqa while True: sig_addr = idaapi.bin_search( sig_addr, self.TEXT_SEGMENT.end_ea, # noqa fmt_sig, sig_mask, ida_bytes.BIN_SEARCH_FORWARD, ida_bytes.BIN_SEARCH_NOCASE) # No more results if sig_addr == idaapi.BADADDR: break # We have a match result_count += 1 # But more than one fails the signature if result_count > 1: return False # Starting at the match, returns the same match. So go to the next byte sig_addr += 1 return True