diff --git a/qiling/loader/macho.py b/qiling/loader/macho.py index a7e7f87bc..a98fc01db 100644 --- a/qiling/loader/macho.py +++ b/qiling/loader/macho.py @@ -422,8 +422,95 @@ def loadMacho(self, depth=0, isdyld=False): if self.ql.arch.type == QL_ARCH.X8664: load_commpage(self.ql) + if depth == 0 and self.is_driver is False and self.ql.arch.type == QL_ARCH.ARM64: + self.dyld_chained_fixups() + return self.proc_entry - + + def dyld_chained_fixups(self): + # Only support arm64 for now + all_imports = {} + + chained_fixups = self.macho_file.chained_fixups + if chained_fixups is None: + return + + can_resolve_binds = chained_fixups.header.imports_format == 1 # Only support format 1 for now + for starts_in_seg in chained_fixups.starts_in_segment: + if starts_in_seg is None: + continue + + pointer_format = starts_in_seg.pointer_format + for page_idx in range(starts_in_seg.page_count): + start_offset = starts_in_seg.page_start[page_idx] + if start_offset == 0xFFFF: + continue + + page_file_offset = starts_in_seg.segment_offset + (page_idx * starts_in_seg.page_size) + chain_cursor_ptr = self.load_address + self.slide + page_file_offset + start_offset + done = False + + while not done: + target_offset = 0 + if pointer_format == DYLD_CHAINED_PTR_64: + value = self.ql.unpack64(self.ql.mem.read(chain_cursor_ptr, 8)) + target = value & 0xFFFFFFFFF + high8 = (value >> 36) & 0xFF + next_stride = (value >> 51) & 0xFFF + is_bind = (value >> 63) & 0x1 == 1 + if is_bind is False: target_offset = target | (high8 << 36) + else: + raise QlErrorMACHOFormat("Unsupported pointer format in chained fixups: {}".format(pointer_format)) + + if is_bind is False: + corrected_addr = self.load_address + self.slide + target_offset + if pointer_format == DYLD_CHAINED_PTR_32: + self.ql.mem.write(chain_cursor_ptr, self.ql.pack32(corrected_addr)) + else: + self.ql.mem.write(chain_cursor_ptr, self.ql.pack64(corrected_addr)) + else: + if not can_resolve_binds: + raise QlErrorMACHOFormat("Cannot resolve binds in chained fixups") + + ordinal = 0 + if pointer_format == DYLD_CHAINED_PTR_64: + value = self.ql.unpack64(self.ql.mem.read(chain_cursor_ptr, 8)) + ordinal = value & 0xFFFFFF + else: + raise QlErrorMACHOFormat("Unsupported pointer format in chained fixups: {}".format(pointer_format)) + + if ordinal < chained_fixups.header.imports_count: + import_entry = chained_fixups.imports[ordinal] + all_imports[chain_cursor_ptr] = import_entry.symbol_name + + if next_stride == 0: + done = True + else: + chain_cursor_ptr += next_stride * 4 + + + self.import_symbols = {} + if len(all_imports) != 0: + self.static_addr = self.vm_end_addr + self.static_size = self.ql.mem.align_up(len(all_imports) * 4) + + self.ql.mem.map(self.static_addr, self.static_size, info="[STATIC]") + self.vm_end_addr += self.static_size + self.ql.log.info("Memory for external static symbol is created at 0x%x with size 0x%x" % (self.static_addr, + self.static_size)) + jump = self.static_addr + for fixup_addr in all_imports: + self.import_symbols[jump] = { + 'ptr': fixup_addr, + 'name': all_imports[fixup_addr] + } + + #self.ql.mem.write(jump, b'\x00\x00\x20\xD4') # brk #0 + self.ql.mem.write(jump, b'\xC0\x03\x5F\xD6') # ret + self.ql.mem.write(fixup_addr, self.ql.pack64(jump)) + jump += 4 + + def loadSegment64(self, cmd, isdyld): PAGE_SIZE = 0x1000 if isdyld: diff --git a/qiling/loader/macho_parser/const.py b/qiling/loader/macho_parser/const.py index 0726f374d..f7cecb87a 100644 --- a/qiling/loader/macho_parser/const.py +++ b/qiling/loader/macho_parser/const.py @@ -56,4 +56,29 @@ # UNIXTHREAD X86_THREAD_STATE32 = 0x00000001 X86_THREAD_STATE64 = 0x00000004 -ARM_THREAD_STATE64 = 0x00000006 \ No newline at end of file +ARM_THREAD_STATE64 = 0x00000006 + + +SECTION_TYPE = 0x000000ff + +# S_NON_LAZY_SYMBOL_POINTERS - Section with non-lazy symbol pointers. +S_NON_LAZY_SYMBOL_POINTERS = 0x06 + +# S_LAZY_SYMBOL_POINTERS - Section with lazy symbol pointers. +S_LAZY_SYMBOL_POINTERS = 0x07 + +INDIRECT_SYMBOL_ABS = 0x40000000 +INDIRECT_SYMBOL_LOCAL = 0x80000000 + +# Pointer Formats +DYLD_CHAINED_PTR_ARM64E = 1 +DYLD_CHAINED_PTR_64 = 2 +DYLD_CHAINED_PTR_32 = 3 +DYLD_CHAINED_PTR_32_CACHE = 4 +DYLD_CHAINED_PTR_32_FIRMWARE = 5 +DYLD_CHAINED_PTR_64_OFFSET = 6 +DYLD_CHAINED_PTR_ARM64E_OFFSET = 7 # aka KERNEL +DYLD_CHAINED_PTR_64_KERNEL_CACHE = 8 +DYLD_CHAINED_PTR_ARM64E_USERLAND24 = 9 +DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE = 10 +DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE = 11 diff --git a/qiling/loader/macho_parser/data.py b/qiling/loader/macho_parser/data.py index 7acda503a..cf1704e67 100644 --- a/qiling/loader/macho_parser/data.py +++ b/qiling/loader/macho_parser/data.py @@ -39,6 +39,9 @@ def __init__(self, lc, data): self.rel_offset = lc.relocations_offset self.rel_num = lc.number_of_relocations self.flags = lc.flags + self.reserved1 = lc.reserved1 + self.reserved2 = lc.reserved2 + self.reserved3 = lc.reserved3 self.content = data[self.offset : self.offset + self.size] # def __str__(self): @@ -235,3 +238,116 @@ def __init__(self, lc, data): def __str__(self): pass + +class DyldChainedHeader: + def __init__(self, data): + ''' + struct dyld_chained_header { + uint32_t fixups_version; + uint32_t starts_offset; + uint32_t imports_offset; + uint32_t symbols_offset; + uint32_t imports_count; + uint32_t imports_format; + uint32_t symbols_format; + }; + ''' + self.fixups_version = unpack(">= 8 + self.weak_import = tmp & 0x1 + tmp >>= 1 + self.name_offset = tmp & 0x7fffff + + self.symbol_name = None # to be filled later + +class ChainedFixups: + def __init__(self, lc, data): + self.offset = lc.data_offset + self.size = lc.data_size + self.content = data[self.offset : self.offset + self.size] + + self.header = DyldChainedHeader(self.content) + self.starts_in_image = DyldChainedStartsInImage(self.content[self.header.starts_offset:]) + self.starts_in_segment = [] + for i in range(self.starts_in_image.seg_count): + seg_offset = self.starts_in_image.seg_info_offset[i] + if seg_offset == 0: + self.starts_in_segment.append(None) + continue + + seg_data = self.content[self.header.starts_offset + seg_offset:] + self.starts_in_segment.append(DyldChainedStartsInSegment(seg_data)) + + self.imports = [] + symbol_pool = self.content[self.header.symbols_offset:] + for i in range(self.header.imports_count): + import_offset = self.header.imports_offset + i * 4 + import_data = self.content[import_offset : import_offset + 4] + + import_entry = DyldChainedImport(import_data) + import_entry.symbol_name = symbol_pool[import_entry.name_offset :].split(b'\0', 1)[0].decode() + self.imports.append(import_entry) + + + + # def __str__(self): + # return (" ChainedFixupsInfo: content {}".format(self.content)) diff --git a/qiling/loader/macho_parser/loadcommand.py b/qiling/loader/macho_parser/loadcommand.py index 1eb18d362..314ba20c0 100644 --- a/qiling/loader/macho_parser/loadcommand.py +++ b/qiling/loader/macho_parser/loadcommand.py @@ -45,7 +45,8 @@ def get_complete(self): LC_DYLD_CHAINED_FIXUPS : LoadDyldChainedFixups, LC_RPATH : LoadRPath, LC_ID_DYLIB : LoadIdDylib, - LC_BUILD_VERSION : LoadBuildVersion + LC_BUILD_VERSION : LoadBuildVersion, + LC_DYLD_CHAINED_FIXUPS : LoadDyldChainedFixups, } exec_func = cmd_map.get(self.cmd_id) @@ -537,4 +538,11 @@ def __init__(self, data): self.current_version = unpack("