Esempio n. 1
0
    def compile_moved_injected_code(self,
                                    classified_instructions,
                                    patch_code,
                                    offset=0,
                                    is_thumb=False):
        # create injected_code (pre, injected, culprit, post, jmp_back)
        injected_code = "_patcherex_begin_patch:\n"
        injected_code += "\n".join([
            utils.capstone_to_nasm(i) for i in classified_instructions
            if i.overwritten == 'pre'
        ])
        injected_code += "\n"
        injected_code += "; --- custom code start\n" + patch_code + "\n" + "; --- custom code end\n" + "\n"
        injected_code += "\n".join([
            utils.capstone_to_nasm(i) for i in classified_instructions
            if i.overwritten == 'culprit'
        ])
        injected_code += "\n"
        injected_code += "\n".join([
            utils.capstone_to_nasm(i) for i in classified_instructions
            if i.overwritten == 'post'
        ])
        injected_code += "\n"
        jmp_back_target = None
        for i in reversed(
                classified_instructions
        ):  # jmp back to the one after the last byte of the last non-out
            if i.overwritten != "out":
                jmp_back_target = i.address + len(i.bytes)
                break
        assert jmp_back_target is not None
        injected_code += "jmp %s" % hex(int(jmp_back_target) - offset) + "\n"
        # removing blank lines
        injected_code = "\n".join(
            [line for line in injected_code.split("\n") if line != ""])
        l.debug("injected code:\n%s", injected_code)

        compiled_code = utils.compile_asm(
            injected_code,
            base=self.get_current_code_position(),
            name_map=self.name_map,
            bits=self.structs.elfclass)
        return compiled_code
Esempio n. 2
0
    def find_detour_pos(self, block, detour_size, patch_addr):
        # iterates through the instructions to find where the detour can be stored
        movable_instructions = self.get_movable_instructions(block)

        movable_bb_start = movable_instructions[0].address
        movable_bb_size = self.project.factory.block(
            block.addr, num_inst=len(movable_instructions)).size
        l.debug("movable_bb_size: %d", movable_bb_size)
        l.debug(
            "movable bb instructions:\n%s", "\n".join(
                [utils.instruction_to_str(i) for i in movable_instructions]))

        # find a spot for the detour
        detour_pos = None
        for detour_start in range(
                movable_bb_start,
                movable_bb_start + movable_bb_size - detour_size, 4):
            if detour_start in [i.address for i in movable_instructions]:
                detour_pos = detour_start
                break
        if detour_pos is None:
            raise DetourException("No space in bb", hex(block.addr),
                                  hex(block.size), hex(movable_bb_start),
                                  hex(movable_bb_size))
        l.debug("detour fits at %s", hex(detour_pos))

        return detour_pos
Esempio n. 3
0
    def apply_patches(self, patches):
        # deal with stackable patches
        # add stackable patches to the one with highest priority
        insert_code_patches = [
            p for p in patches if isinstance(p, InsertCodePatch)
        ]
        insert_code_patches_dict = defaultdict(list)
        for p in insert_code_patches:
            insert_code_patches_dict[p.addr].append(p)
        insert_code_patches_dict_sorted = defaultdict(list)
        for k, v in insert_code_patches_dict.items():
            insert_code_patches_dict_sorted[k] = sorted(
                v, key=lambda x: -1 * x.priority)

        insert_code_patches_stackable = [
            p for p in patches
            if isinstance(p, InsertCodePatch) and p.stackable
        ]
        for sp in insert_code_patches_stackable:
            assert len(sp.dependencies) == 0
            if sp.addr in insert_code_patches_dict_sorted:
                highest_priority_at_addr = insert_code_patches_dict_sorted[
                    sp.addr][0]
                if highest_priority_at_addr != sp:
                    highest_priority_at_addr.asm_code += "\n" + sp.asm_code + "\n"
                    patches.remove(sp)

        #deal with AddLabel patches
        lpatches = [p for p in patches if (isinstance(p, AddLabelPatch))]
        for p in lpatches:
            self.name_map[p.name] = p.addr

        # check for duplicate labels, it is not very necessary for this backend
        # but it is better to behave in the same way of the reassembler backend
        relevant_patches = [
            p for p in patches
            if isinstance(p, (AddCodePatch, AddEntryPointPatch,
                              InsertCodePatch))
        ]
        all_code = ""
        for p in relevant_patches:
            if isinstance(p, InsertCodePatch):
                code = p.code
            else:
                code = p.asm_code
            all_code += "\n" + code + "\n"
        labels = utils.string_to_labels(all_code)
        duplicates = set(x for x in labels if labels.count(x) > 1)
        if len(duplicates) > 1:
            raise DuplicateLabelsException(
                "found duplicate assembly labels: %s" % (str(duplicates)))

        # for now any added code will be executed by jumping out and back ie CGRex
        # apply all add code patches
        self.added_code_file_start = len(self.ncontent)
        self.name_map.force_insert("ADDED_CODE_START",
                                   (len(self.ncontent) % 0x1000) +
                                   self.added_code_segment)

        # 0) RawPatch:
        for patch in patches:
            if isinstance(patch, RawFilePatch):
                self.ncontent = utils.bytes_overwrite(self.ncontent,
                                                      patch.data,
                                                      patch.file_addr)
                self.added_patches.append(patch)
                l.info("Added patch: %s", str(patch))
        for patch in patches:
            if isinstance(patch, RawMemPatch):
                self.patch_bin(patch.addr, patch.data)
                self.added_patches.append(patch)
                l.info("Added patch: %s", str(patch))

        for patch in patches:
            if isinstance(patch, RemoveInstructionPatch):
                if patch.ins_size is None:
                    size = 4
                else:
                    size = patch.ins_size
                self.patch_bin(patch.ins_addr, b"\x60\x00\x00\x00" * int(
                    (size + 4 - 1) / 4))
                self.added_patches.append(patch)
                l.info("Added patch: %s", str(patch))

        # 5.5) ReplaceFunctionPatch (preprocessing rodata)
        for patch in patches:
            if isinstance(patch, ReplaceFunctionPatch):
                patches += self.compile_function(patch.asm_code,
                                                 entry=patch.addr,
                                                 symbols=patch.symbols,
                                                 data_only=True,
                                                 prefix="_RFP" +
                                                 str(patches.index(patch)))

        # 1) Add{RO/RW/RWInit}DataPatch
        self.added_data_file_start = len(self.ncontent)
        curr_data_position = self.name_map["ADDED_DATA_START"]
        for patch in patches:
            if isinstance(
                    patch,
                (AddRWDataPatch, AddRODataPatch, AddRWInitDataPatch)):
                if hasattr(patch, "data"):
                    final_patch_data = patch.data
                else:
                    final_patch_data = b"\x00" * patch.len
                self.added_data += final_patch_data
                if patch.name is not None:
                    self.name_map[patch.name] = curr_data_position
                curr_data_position += len(final_patch_data)
                self.ncontent = utils.bytes_overwrite(self.ncontent,
                                                      final_patch_data)
                self.added_patches.append(patch)
                l.info("Added patch: %s", str(patch))
        self.ncontent = utils.pad_bytes(
            self.ncontent, 0x10)  # some minimal alignment may be good

        self.added_code_file_start = len(self.ncontent)
        if self.replace_note_segment:
            self.name_map.force_insert(
                "ADDED_CODE_START",
                int((curr_data_position + 0x10 - 1) / 0x10) * 0x10)
        else:
            self.name_map.force_insert("ADDED_CODE_START",
                                       (len(self.ncontent) % 0x1000) +
                                       self.added_code_segment)

        # 2) AddCodePatch
        # resolving symbols
        current_symbol_pos = self.get_current_code_position()
        for patch in patches:
            if isinstance(patch, AddCodePatch):
                if patch.is_c:
                    code_len = len(
                        self.compile_c(patch.asm_code,
                                       optimization=patch.optimization,
                                       compiler_flags=patch.compiler_flags))
                else:
                    code_len = len(
                        self.compile_asm(patch.asm_code, current_symbol_pos))
                if patch.name is not None:
                    self.name_map[patch.name] = current_symbol_pos
                current_symbol_pos += code_len
        # now compile for real
        for patch in patches:
            if isinstance(patch, AddCodePatch):
                if patch.is_c:
                    new_code = self.compile_c(
                        patch.asm_code,
                        optimization=patch.optimization,
                        compiler_flags=patch.compiler_flags)
                else:
                    new_code = self.compile_asm(
                        patch.asm_code, self.get_current_code_position(),
                        self.name_map)
                self.added_code += new_code
                self.ncontent = utils.bytes_overwrite(self.ncontent, new_code)
                self.added_patches.append(patch)
                l.info("Added patch: %s", str(patch))

        # 3) AddEntryPointPatch
        # basically like AddCodePatch but we detour by changing oep
        # and we jump at the end of all of them
        # resolving symbols
        for patch in patches:
            if isinstance(patch, AddEntryPointPatch):
                old_oep = self.get_oep()
                new_oep = self.get_current_code_position()
                # ref: glibc/sysdeps/{ARCH}/start.S
                instructions = patch.asm_code
                instructions += "\nb {}".format(hex(int(old_oep)))

                new_code = self.compile_asm(instructions,
                                            self.get_current_code_position(),
                                            self.name_map)
                self.added_code += new_code
                self.added_patches.append(patch)
                self.ncontent = utils.bytes_overwrite(self.ncontent, new_code)
                self.set_oep(new_oep)
                l.info("Added patch: %s", str(patch))

        # 4) InlinePatch
        # we assume the patch never patches the added code
        for patch in patches:
            if isinstance(patch, InlinePatch):
                new_code = self.compile_asm(patch.new_asm,
                                            patch.instruction_addr,
                                            self.name_map)
                # Limiting the inline patch to a single block is not necessary
                # assert len(new_code) <= self.project.factory.block(patch.instruction_addr, num_inst=patch.num_instr, max_size=).size
                file_offset = self.project.loader.main_object.addr_to_offset(
                    patch.instruction_addr)
                self.ncontent = utils.bytes_overwrite(self.ncontent, new_code,
                                                      file_offset)
                self.added_patches.append(patch)
                l.info("Added patch: %s", str(patch))

        # 5) InsertCodePatch
        # these patches specify an address in some basic block, In general we will move the basic block
        # and fix relative offsets
        # With this backend heer we can fail applying a patch, in case, resolve dependencies
        insert_code_patches = [
            p for p in patches if isinstance(p, InsertCodePatch)
        ]
        insert_code_patches = sorted(insert_code_patches,
                                     key=lambda x: -1 * x.priority)
        applied_patches = []
        while True:
            name_list = [
                str(p) if (p is None or p.name is None) else p.name
                for p in applied_patches
            ]
            l.info("applied_patches is: |%s|", "-".join(name_list))
            assert all(a == b
                       for a, b in zip(applied_patches, insert_code_patches))
            for patch in insert_code_patches[len(applied_patches):]:
                self.save_state(applied_patches)
                try:
                    l.info("Trying to add patch: %s", str(patch))
                    if patch.name is not None:
                        self.name_map[
                            patch.name] = self.get_current_code_position()
                    new_code = self.insert_detour(patch)
                    self.added_code += new_code
                    self.ncontent = utils.bytes_overwrite(
                        self.ncontent, new_code)
                    applied_patches.append(patch)
                    self.added_patches.append(patch)
                    l.info("Added patch: %s", str(patch))
                except (DetourException, MissingBlockException,
                        DoubleDetourException) as e:
                    l.warning(e)
                    insert_code_patches, removed = self.handle_remove_patch(
                        insert_code_patches, patch)
                    #print map(str,removed)
                    applied_patches = self.restore_state(
                        applied_patches, removed)
                    l.warning(
                        "One patch failed, rolling back InsertCodePatch patches. Failed patch: %s",
                        str(patch))
                    break
                    # TODO: right now rollback goes back to 0 patches, we may want to go back less
                    # the solution is to save touched_bytes and ncontent indexed by applied patfch
                    # and go back to the biggest compatible list of patches
            else:
                break  #at this point we applied everything in current insert_code_patches
                # TODO symbol name, for now no name_map for InsertCode patches

        header_patches = [InsertCodePatch,InlinePatch,AddEntryPointPatch,AddCodePatch, \
                AddRWDataPatch,AddRODataPatch,AddRWInitDataPatch]

        # 5.5) ReplaceFunctionPatch
        for patch in patches:
            if isinstance(patch, ReplaceFunctionPatch):
                if self.structs.elfclass == 64:
                    # reloc type not supported (TOC info is in executables but not in object file, but relocs in object file will need TOC info.)
                    raise Exception(
                        "ReplaceFunctionPatch: PPC64 not yet supported")
                for k, v in self.name_map.items():
                    if k.startswith("_RFP" + str(patches.index(patch))):
                        patch.symbols[k[len("_RFP" +
                                            str(patches.index(patch))):]] = v
                new_code = self.compile_function(
                    patch.asm_code,
                    bits=self.structs.elfclass,
                    little_endian=self.structs.little_endian,
                    entry=patch.addr,
                    symbols=patch.symbols)
                file_offset = self.project.loader.main_object.addr_to_offset(
                    patch.addr)
                self.ncontent = utils.bytes_overwrite(
                    self.ncontent, b"\x60\x00\x00\x00" * (patch.size // 4),
                    file_offset)
                if (patch.size >= len(new_code)):
                    file_offset = self.project.loader.main_object.addr_to_offset(
                        patch.addr)
                    self.ncontent = utils.bytes_overwrite(
                        self.ncontent, new_code, file_offset)
                else:
                    header_patches.append(ReplaceFunctionPatch)
                    detour_pos = self.get_current_code_position()
                    offset = self.project.loader.main_object.mapped_base if self.project.loader.main_object.pic else 0
                    new_code = self.compile_function(
                        patch.asm_code,
                        bits=self.structs.elfclass,
                        little_endian=self.structs.little_endian,
                        entry=detour_pos + offset,
                        symbols=patch.symbols)
                    self.added_code += new_code
                    self.ncontent = utils.bytes_overwrite(
                        self.ncontent, new_code)
                    # compile jmp
                    jmp_code = self.compile_jmp(patch.addr,
                                                detour_pos + offset)
                    self.patch_bin(patch.addr, jmp_code)
                self.added_patches.append(patch)
                l.info("Added patch: %s", str(patch))

        if any(isinstance(p,ins) for ins in header_patches for p in self.added_patches) or \
                any(isinstance(p,SegmentHeaderPatch) for p in patches):
            # either implicitly (because of a patch adding code or data) or explicitly, we need to change segment headers

            # 6) SegmentHeaderPatch
            segment_header_patches = [
                p for p in patches if isinstance(p, SegmentHeaderPatch)
            ]
            if len(segment_header_patches) > 1:
                msg = "more than one patch tries to change segment headers: %s", "|".join(
                    [str(p) for p in segment_header_patches])
                raise IncompatiblePatchesException(msg)
            if len(segment_header_patches) == 1:
                segment_patch = segment_header_patches[0]
                segments = segment_patch.segment_headers
                l.info("Added patch: %s", str(segment_patch))
            else:
                segments = self.modded_segments

            for patch in [
                    p for p in patches if isinstance(p, AddSegmentHeaderPatch)
            ]:
                # add after the first
                segments = [segments[0]] + [patch.new_segment] + segments[1:]

            self.setup_headers(segments)
            self.set_added_segment_headers()
            l.debug("final symbol table: %s",
                    repr([(k, hex(v)) for k, v in self.name_map.items()]))
        else:
            l.info("no patches, the binary will not be touched")
Esempio n. 4
0
    def insert_detour(self, patch):
        detour_size = 4
        ppc_nop = b"\x60\x00\x00\x00"

        if self.try_without_cfg:
            offset = self.project.loader.main_object.mapped_base if self.project.loader.main_object.pic else 0
            detour_jmp_code = self.compile_jmp(
                patch.addr,
                self.get_current_code_position() + offset)
            patched_bbcode = self.read_mem_from_file(patch.addr, detour_size)
            patched_bbinstructions = self.disassemble(patched_bbcode,
                                                      patch.addr)
            l.debug(
                "patched bb instructions:\n %s", "\n".join([
                    utils.instruction_to_str(i) for i in patched_bbinstructions
                ]))
            self.patch_bin(patch.addr, detour_jmp_code)
            new_code = self.compile_asm(
                patch.code + "\n" + "\n".join(
                    [self.capstone_to_asm(s)
                     for s in patched_bbinstructions]) +
                "\nb %s" % hex(patch.addr + 4 - offset),
                base=self.get_current_code_position(),
                name_map=self.name_map)
            return new_code
        # TODO allow special case to patch syscall wrapper epilogue
        # (not that important since we do not want to patch epilogue in syscall wrapper)
        block_addr = self.get_block_containing_inst(patch.addr)
        mem = self.read_mem_from_file(
            block_addr,
            self.project.factory.block(block_addr).size)
        block = self.project.factory.block(block_addr, byte_string=mem)

        l.debug("inserting detour for patch: %s",
                (map(hex, (block_addr, block.size, patch.addr))))

        # get movable instructions
        movable_instructions = self.get_movable_instructions(block)
        if len(movable_instructions) == 0:
            raise DetourException("No movable instructions found")

        # figure out where to insert the detour
        detour_pos = self.find_detour_pos(block, detour_size, patch.addr)

        # classify overwritten instructions
        detour_overwritten_bytes = range(detour_pos, detour_pos + detour_size)

        for i in movable_instructions:
            if len(
                    set(detour_overwritten_bytes).intersection(
                        set(range(i.address, i.address + len(i.bytes))))) > 0:
                if i.address < patch.addr:
                    i.overwritten = "pre"
                elif i.address == patch.addr:
                    i.overwritten = "culprit"
                else:
                    i.overwritten = "post"
            else:
                i.overwritten = "out"
        l.debug("\n".join(
            [utils.instruction_to_str(i) for i in movable_instructions]))
        assert any(i.overwritten != "out" for i in movable_instructions)

        # replace overwritten instructions with nops
        for i in movable_instructions:
            if i.overwritten != "out":
                for b in range(i.address, i.address + len(i.bytes)):
                    if b in self.touched_bytes:
                        raise DoubleDetourException(
                            "byte has been already touched: %08x" % b)
                    self.touched_bytes.add(b)
                self.patch_bin(i.address, ppc_nop)

        # insert the jump detour
        offset = self.project.loader.main_object.mapped_base if self.project.loader.main_object.pic else 0
        detour_jmp_code = self.compile_jmp(
            detour_pos,
            self.get_current_code_position() + offset)
        self.patch_bin(detour_pos, detour_jmp_code)
        patched_bbcode = self.read_mem_from_file(block_addr, block.size)
        patched_bbinstructions = self.disassemble(patched_bbcode, block_addr)
        l.debug(
            "patched bb instructions:\n %s", "\n".join(
                [utils.instruction_to_str(i) for i in patched_bbinstructions]))

        new_code = self.compile_moved_injected_code(movable_instructions,
                                                    patch.code,
                                                    offset=offset)

        return new_code