def patch_word(ea, value, wordsize=WORD_SIZE): """Patch the word at the given address. Words are patched using PatchByte(), PatchWord(), PatchDword(), or PatchQword(), as appropriate. """ if wordsize == 1: ida_bytes.patch_byte(ea, value) elif wordsize == 2: ida_bytes.patch_word(ea, value) elif wordsize == 4: ida_bytes.patch_dword(ea, value) elif wordsize == 8: ida_bytes.patch_qword(ea, value) else: raise ValueError('Invalid argument: wordsize={}'.format(wordsize))
def req_patch(self, hash): addr, value, length = hash['addr'], hash['value'], hash['len'] if length == 4: prev_value = idc.get_wide_dword(addr) if not ida_bytes.create_data(ea, FF_DWORD, 4, ida_idaapi.BADADDR): rs_log('[x] ida_bytes.create_data FF_DWORD failed') if not ida_bytes.patch_dword(addr, value): rs_log('[x] patch_dword failed') if not idc.op_plain_offset(addr, 0, 0): rs_log('[x] op_plain_offset failed') elif length == 8: prev_value = idc.get_qword(addr) if not ida_bytes.create_data(addr, FF_QWORD, 8, ida_idaapi.BADADDR): rs_log('[x] ida_bytes.create_data FF_QWORD failed') if not ida_bytes.patch_qword(addr, value): rs_log('[x] patch_qword failed') if not idc.op_plain_offset(addr, 0, 0): rs_log('[x] op_plain_offset failed') else: rs_log("[x] unsupported length: %d" % length) return rs_log("patched 0x%x = 0x%x (previous was 0x%x)" % (addr, value, prev_value))
def add_method_xref(self, xref): msg("Adding cross reference to method implementation for %s\n" % get_func_name(self.method_pointer)) add_dref(xref.frm, self.method_pointer, dr_I | XREF_USER) offset = self.method_pointer - xref.frm instruction_bytes = get_bytes(xref.frm, self.ARM64_INSTRUCTION_SIZE) #TODO: are there other instructions that could reference a method selector #and then move the selector reference into a register? arm64_ldr = AArch64LDRInstruction(instruction_bytes) arm64_ldr.patch_offset(offset) patch_dword(xref.frm, arm64_ldr.instruction_int) return ObjcMethodXref(xref.frm, self.method_pointer, xref.to)
def set_dword(ea, value): """ Static method allowing to set the value of one dwordat an address. :param ea: The address at which changing the value. :param value: The value to set at the address. :raise RuntimeError: If it was not possible to change the value. """ if not ida_bytes.patch_dword(ea, value): raise RuntimeError("Unable to set value {} at {}".format(ea, value))
def resolve_opaque_push_retn(): PATTERNS = ["68 ?? ?? ?? ?? C3"] count_patched = 0 for pattern in PATTERNS: ea = 0 while ea != BADADDR: ea = ida_search.find_binary( ea, BADADDR, pattern, 16, SEARCH_NEXT | SEARCH_DOWN | SEARCH_CASE) ''' pattern: 68 ?? ?? ?? ?? C3 .text:00406922 68 B2 6A 00 10 push 0x10006ab2 .text:00406927 C3 retn patched: .text:00406922 E9 B2 6A 00 10 jmp 0x10006ab2 .text:00406927 90 nop ''' if ea_in_bounds(ea): j_pos = ea pos_jmp = idc.get_wide_dword(j_pos + 1) # absolute address offset = pos_jmp - 5 - j_pos # Patch the push and retn instructions with NOPs (6 bytes) for i in range(0, 6): ida_bytes.patch_byte(j_pos + i, 0x90) ida_bytes.patch_byte(j_pos, 0xE9) ida_bytes.patch_dword(j_pos + 0x1, offset) idc.create_insn(ea) count_patched += 1 print("\tPatched resolve_opaque_push_retn: {0}".format(count_patched))
def resolve_obf_calls(ea): next_instr_addr = idc.get_wide_dword(ea + 1) first_jmp_target = (ea + idc.get_wide_dword(ea + 0x7) + 0xB) & 0xFFFFFFFF second_jmp_target = (ea + idc.get_wide_dword(ea + 0xD) + 0x11) & 0xFFFFFFFF if first_jmp_target != second_jmp_target: return call_param = (first_jmp_target - ea - 5) & 0xFFFFFFFF # Now we can replace all code till next instruction's address with NOPs fill_with_nops(ea, next_instr_addr) # Insert CALL ida_bytes.patch_byte(ea, 0xE8) # CALL ida_bytes.patch_dword(ea + 1, call_param) idc.create_insn(ea) return True
def add_method_xref(self, xref): msg("Adding cross reference to method implementation for %s\n" % get_func_name(self.method_pointer)) #TODO: clean this up so it's more clear how we're parsing and patching the instruction #TODO: handle other potential instructions that could place a method selref into a register #TODO: sanity check what instruction we're actually working with before blindly deciding # it's a 7-byte mov instruction add_dref(xref.frm, self.method_pointer, dr_I | XREF_USER) #offset is a rip-relative offset that gets added to rip and dereferenced #when this instruction is executed, rip will be pointing to the next instruction #meaning it has been incremented by 7 (the length of the mov instruction) offset = self.method_pointer - xref.frm - self.X86_64_MOV_INSTRUCTION_SIZE #this replaces mov RSI, &selector with: # mov RSI, &method #xref.frm is the address of the mov instruction #+3 (4th byte of the instruction) #is where the RIP-relative operand is that #will get dereferenced as a pointer patch_dword(xref.frm + 3, offset) return ObjcMethodXref(xref.frm, self.method_pointer, xref.to)
def value(self, value): """ Property setter which allow to set the value of this object. This property works only if the :meth:`~BipData.is_numerable` property returned True. If this object has no data (:meth:`~BipData.has_data` property return False) or is unknown (:meth:`~BipData.is_unknwon` return True) the value set is considered to be on 1 byte. For setting non numerical value or value on more than 8 bytes use the :meth:`~BipElt.bytes` property setter. This property is link to the type defined or guessed by IDA and it is a good idea to assure you have the proper type before using it. :param int value: An integer to which set the value of the current data element. :raise RuntimeError: If the setting of the value failed or if the value could not be set because of an unknown type. """ if self.value == value: # case where we are setting at the same value return if (not self.has_data) or self.is_unknown or self.is_byte: if not ida_bytes.patch_byte(self.ea, value): raise RuntimeError("Unable to patch value: {}".format(self)) elif self.is_word: if not ida_bytes.patch_word(self.ea, value): raise RuntimeError("Unable to patch value: {}".format(self)) elif self.is_dword: if not ida_bytes.patch_dword(self.ea, value): raise RuntimeError("Unable to patch value: {}".format(self)) elif self.is_qword: if not ida_bytes.patch_qword(self.ea, value): raise RuntimeError("Unable to patch value: {}".format(self)) else: raise RuntimeError("Unable to patch value: {}".format(self))
def resolve_opaque_jnz_jz(): PATTERNS = [ "0F 84 ?? ?? ?? ?? 0F 85 ?? ?? ?? ??", # jz + jnz "0F 85 ?? ?? ?? ?? 0F 84 ?? ?? ?? ??", # jnz + jz "74 ?? 0F 85 ?? ?? ?? ??", # jz short + jnz "0F 84 ?? ?? ?? ?? 75 ??", # jz + jnz short "75 ?? 0F 84 ?? ?? ?? ??", # jnz short + jz "0F 85 ?? ?? ?? ?? 74 ??", # jnz + jz short "74 ?? 75 ??", # jz short + jnz short "75 ?? 74 ??" # jnz short + jz short ] count_patched = 0 count_not_patched = 0 for pattern in PATTERNS: ea = 0 while ea != BADADDR: ea = ida_search.find_binary( ea, BADADDR, pattern, 16, SEARCH_NEXT | SEARCH_DOWN | SEARCH_CASE) ''' pattern: 0F 85 ?? ?? ?? ?? 0F 84 ?? ?? ?? ?? .text:0040690E 66 21 CF and di, cx .text:00406911 0F 85 AE F4 FF FF jnz loc_405DC5 <- j_1_pos .text:00406917 0F 84 A8 F4 FF FF jz loc_405DC5 <- j_2_pos patched: .text:0040690E 66 21 CF and di, cx .text:00406911 E9 A9 F4 FF FF jmp loc_405DC5 .text:00406916 90 nop .text:00406917 90 nop .text:00406918 90 nop .text:00406919 90 nop .text:0040691a 90 nop .text:0040691b 90 nop .text:0040691c 90 nop ''' if ea_in_bounds(ea): # .text:00406911 0F 85 AE F4 FF FF jnz loc_405DC5 <- j_1_pos # AE F4 FF FF <- j_1_value Relative offset value j_1_pos = ea j_1_value, j_1_size = get_j_val(j_1_pos) j_2_pos = ea + j_1_size j_2_value, j_2_size = get_j_val(j_2_pos) pos_jmp = j_1_pos + j_1_value + j_1_size if j_1_value - j_2_value == j_2_size: # Patch the jz and jnz instructions with NOPs for i in range(0, j_1_size + j_2_size): ida_bytes.patch_byte(j_1_pos + i, 0x90) if j_1_size == 2: # jz short or jnz short # Patch with a relative short jmp (size = 2) in the position of the first conditional jmp addr_to_jmp = j_1_value ida_bytes.patch_byte(j_1_pos, 0xEB) ida_bytes.patch_byte(j_1_pos + 0x1, addr_to_jmp) else: # jz or jnz # Patch with a relative jmp (size = 5) in the position of the first conditional jmp addr_to_jmp = j_1_value + 1 ida_bytes.patch_byte(j_1_pos, 0xE9) ida_bytes.patch_dword(j_1_pos + 0x1, addr_to_jmp) idc.create_insn(ea) count_patched += 1 else: count_not_patched += 1 print("\tPatched resolve_opaque_jnz_jz: {0}".format(count_patched)) print("\tNot Patched resolve_opaque_jnz_jz: {0}".format(count_not_patched))