def apply(self): """ Apply patches to found opaque branch :return: None """ for src, dst in self._flow_patches_map.items(): ir_block: IRBlock = self._ir_cfg.get_block(src) asm_instr = ir_block.assignblks[ir_block.dst_linenb].instr if asm_instr.name not in conditional_branch: log(f"Unsupported asm pattern at {hex(src)}", code='!') continue patch_addr = asm_instr.offset opcode1 = ida_bytes.get_byte(patch_addr) # Fast and Furious if opcode1 == 0x0F: ida_bytes.patch_bytes( patch_addr, b"\xe9" + pack("<I", (dst - (patch_addr + 5)) & (2**32 - 1))) elif ((opcode1 & 0xe0) == 0xe0) or ((opcode1 & 0x70) == 0x70): ida_bytes.patch_byte(patch_addr, 0xeb) ida_bytes.patch_byte(patch_addr + 1, (dst - (patch_addr + 2)) & 0xFF) else: log(f"Unknown first part of opcode at {hex(patch_addr)}", code='!') continue log(f"Apply patch JCC -> JMP at {hex(patch_addr)}")
def write_segment(va_start, va_end, segm_name, data): ida_segment.add_segm(0, va_start, va_end, segm_name, None, 0xe) va = va_start for b in data: ida_bytes.patch_byte(va, ord(b)) va += 1 print("wrote 0x%x bytes to 0x%x [%s]" % (len(data), va_start, segm_name))
def references(function_location, decoding_str): """decodes all decoded strings by a given function by applying decoding_str to every byte. decoding_str should contain encoded_byte, for example: decode.references(0x401000, "(encoded_byte ^ 0xA2) + 0x21")""" for xref in idautils.XrefsTo(function_location): ea = xref.frm # The function needs to be defined for get_arg_addrs to work args = idaapi.get_arg_addrs(ea) encoded = idc.get_operand_value(args[0], 0) decoded = idc.get_operand_value(args[1], 0) decoded_str = "" i = 0 encoded_byte = ida_bytes.get_wide_byte(encoded) while encoded_byte != 0: decoded_byte = eval(decoding_str) decoded_str += chr(decoded_byte) ida_bytes.patch_byte(decoded + i, decoded_byte) i += 1 encoded_byte = ida_bytes.get_wide_byte(encoded + i) ida_bytes.create_strlit(decoded, i, STRTYPE_C) idc.set_cmt(ea, f"Decoded: {decoded_str}", 0) print(f"##At {hex(ea)} decoded: {decoded_str}")
def patch(): ea = idc.find_binary(0, idc.SEARCH_NEXT | idc.SEARCH_DOWN | idc.SEARCH_CASE, PATTERN) start_addr = None pattern_len = _pattern_len(PATTERN) while not (ea == ida_idaapi.BADADDR or idc.get_segm_name(ea) != '.text'): # If we don't have start address saved, save it and search for the next pattern match if not start_addr: start_addr = ea # If start address was saved before, we got end address. It's time to patch else: # Remove marker patterns to prevent re-encryption on the next script run fill_with_nops(start_addr, start_addr + pattern_len) fill_with_nops(ea, ea + pattern_len) # Decrypt the code between markers for addr in range(start_addr + pattern_len, ea): ida_bytes.patch_byte(addr, ~idc.get_wide_byte(addr)) print('[0x%08X..0x%08X] Region patched' % (start_addr, ea + pattern_len)) start_addr = None ea = idc.find_binary( ea, idc.SEARCH_NEXT | idc.SEARCH_DOWN | idc.SEARCH_CASE, PATTERN) print('[PATCHING FINISHED]')
def __setitem__(self, item, value): value_bytes = iterbytes_ord(value) for ea_start, ea_end in self._get_ea_range(item): for ea in range(ea_start, ea_end): try: ida_bytes.patch_byte(ea, next(value_bytes)) except StopIteration: return
def nop(): """Nops-out the current instruction and advance the cursor to the next instruction.""" ea = idaapi.get_screen_ea() num_bytes = idc.get_item_size(ea) for i in range(num_bytes): ida_bytes.patch_byte(ea, 0x90) ea += 1 ida_kernwin.refresh_idaview_anyway() ida_kernwin.jumpto(ea)
def nop_region(ea, size): """replace the given range with NOPs""" logger.debug("nopping region from 0x%x size 0x%x", ea, size) for i in range(ea, ea + size): ida_bytes.del_items(i) for i in range(ea, ea + size): ida_bytes.patch_byte(i, 0x90) ida_auto.auto_make_code(ea)
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 main(): print("[*] Start patching to XOR encoded blocks") ea = ida_kernwin.ask_addr(BADADDR, "What address is encoded block by xor?") xor_key = ida_kernwin.ask_long(0x00, "Waht is key for xor?(0-255)") valid_check(ea, xor_key) print hex(ea) print hex(xor_key) while True: b = ida_bytes.get_byte(ea) if b == 0: break ida_bytes.patch_byte(ea, b ^ xor_key) ea += 1 print("[*] Finished patching to XOR encoded blocks")
def set_byte(ea, value): """ Static method allowing to set the value of one byte at 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_byte(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 bytes(self, value): """ Setter allowing to change the bytes value of the element. .. warning:: No check is made on the size of the array of the setter and it can rewrite more than the size of the element, :param value: A list of int corresponding to the bytes to change. """ if isinstance(value, bytes): ida_bytes.patch_bytes(self.ea, value) elif isinstance(value, list): i = 0 for e in value: ida_bytes.patch_byte(self.ea + i, e) i += 1 else: raise TypeError( "Invalid arg {} for BipElt.bytes setter".format(value))
def set_bytes(ea, byt): """ Static method allowing to set the value of one byte at an address. :param int ea: The address at which changing the value. :param bytes byt: The buffer of bytes to set at the address. If a string is provided in python3 it will be decoded as ``latin-1``. :raise RuntimeError: If it was not possible to change one of the value. """ if is_py3() and isinstance(byt, str): byt = bytearray(byt, 'latin-1') else: byt = bytearray(byt) for i in range(len(byt)): value = byt[i] if not ida_bytes.patch_byte(ea + i, value): raise RuntimeError("Unable to set value {} at {}".format(ea, value))
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))
def MemCopy( dest, src, length ) : for i in xrange(0, length): #if (i < 20): # print "set byte at %#x to %#x" % (dest+i, idc.Byte(src+i)) ida_bytes.patch_byte(dest+i,ida_bytes.get_byte(src+i))
def patch_bytes(ea, buf): for i, b in enumerate(buf): ida_bytes.patch_byte(ea + i, b)
def range_xor(start_addr, end_addr, xor_num): for the_addr in range(start_addr, end_addr + 1): the_byte = get_byte(the_addr) patch_byte(the_addr, the_byte ^ xor_num)
def patch_encrypted_buffer(encrypted_buffer_address, decrypted_buffer): if (encrypted_buffer_address not in patched_addresses): for index in range(0, len(decrypted_buffer)): ida_bytes.patch_byte(encrypted_buffer_address + index, decrypted_buffer[index]) MakeStr(encrypted_buffer_address, encrypted_buffer_address + len(decrypted_buffer)) patched_addresses.append(encrypted_buffer_address)
def __call__(self): ida_bytes.patch_byte(self.ea, self.value)
def fill_with_nops(start, stop): for p in range(start, stop): ida_bytes.patch_byte(p, 0x90) idc.create_insn(p) idc.create_insn(stop)
def revert_patch(self): for b in self.bytes: ida_bytes.patch_byte(b.ea, b.original)
def apply_patch(self): for b in self.bytes: ida_bytes.patch_byte(b.ea, b.patched)
def resolve_fs30(): PATTERNS = ["64 ?? 30 00 00 00", "64 ?? ?? 30 00 00 00"] count_patched = 0 count_not_patched = 0 for pattern in PATTERNS: ea = 0 while ea != BADADDR: ''' pattern: 64 ?? 30 00 00 00 .text:00407644 64 A1 30 00 00 00 mov eax, large fs:30h .text:0040764A 50 push eax .text:0040764B 80 ED AD sub ch, 0ADh .text:0040764E B5 32 mov ch, 32h .text:00407650 66 21 D7 and di, dx .text:00407653 5A pop edx .text:00407654 8A 72 02 mov dh, [edx+2] .text:00407657 84 F6 test dh, dh .text:00407659 74 11 jz short loc_40766C pattern: 64 ?? ?? 30 00 00 00 .text:00406E42 64 8B 15 30 00 00 00 mov edx, large fs:30h .text:00406E49 52 push edx .text:00406E4A 66 35 3D 1B xor ax, 1B3Dh .text:00406E4E 20 CD and ch, cl .text:00406E50 28 D1 sub cl, dl .text:00406E52 59 pop ecx .text:00406E53 8A 51 02 mov dl, [ecx+2] .text:00406E56 84 D2 test dl, dl .text:00406E58 74 11 jz short loc_406E6B patched: .text:00406E42 64 8B 15 30 00 00 00 mov edx, large fs:30h .text:00406E49 52 push edx .text:00406E4A 66 35 3D 1B xor ax, 1B3Dh .text:00406E4E 20 CD and ch, cl .text:00406E50 28 D1 sub cl, dl .text:00406E52 59 pop ecx .text:00406E53 8A 51 02 mov dl, [ecx+2] .text:00406E56 84 D2 test dl, dl .text:00406E58 EB 11 jmp short loc_406E6B <----- ''' ea = ida_search.find_binary( ea, BADADDR, pattern, 16, SEARCH_NEXT | SEARCH_DOWN | SEARCH_CASE) if ea_in_bounds(ea): instr = ida_ua.decode_insn(ea) if instr: # while not jmp related instruction found while ((instr.itype <= idaapi.NN_ja) or (instr.itype >= idaapi.NN_jmpshort)): # move to next instr ea = ea + instr.size instr = ida_ua.decode_insn(ea) # ea contains conditional jmp instruction # Patch with relative unconditional jmp ida_bytes.patch_byte(ea, 0xEB) idc.create_insn(ea) count_patched += 1 else: count_not_patched += 1 print("\tPatched resolve_fs30: {0}".format(count_patched)) print("\tNot Patched resolve_fs30: {0}".format(count_not_patched))
def resolve_loops(): PATTERNS = ["81 FB ?? ?? ?? ?? 75"] count_patched = 0 count_not_patched = 0 for pattern in PATTERNS: ea = 0 while ea != BADADDR: ''' pattern: 81 FB ?? ?? ?? ?? 75 .text:00406AA0 01 C7 add edi, eax .text:00406AA2 66 41 inc cx .text:00406AA4 43 inc ebx .text:00406AA5 81 FB A6 01 00 00 cmp ebx, 1A6h .text:00406AAB 75 F3 jnz short loc_406AA0 patched: .text:00406AA0 01 C7 add edi, eax .text:00406AA2 66 41 inc cx .text:00406AA4 43 inc ebx .text:00406AA5 90 nop .text:00406AA6 90 nop .text:00406AA7 90 nop .text:00406AA8 90 nop .text:00406AA9 90 nop .text:00406AAA 90 nop .text:00406AAB 90 nop .text:00406AAC 90 nop ''' ea = ida_search.find_binary( ea, BADADDR, pattern, 16, SEARCH_NEXT | SEARCH_DOWN | SEARCH_CASE) if ea_in_bounds(ea): # Patch CMP and conditional jmp instructions in order to remove the loop ida_bytes.patch_byte(ea + 0, 0x90) ida_bytes.patch_byte(ea + 1, 0x90) ida_bytes.patch_byte(ea + 2, 0x90) ida_bytes.patch_byte(ea + 3, 0x90) ida_bytes.patch_byte(ea + 4, 0x90) ida_bytes.patch_byte(ea + 5, 0x90) ida_bytes.patch_byte(ea + 6, 0x90) ida_bytes.patch_byte(ea + 7, 0x90) idc.create_insn(ea) count_patched += 1 print("\tPatched resolve_loops: {0}".format(count_patched)) print("\tNot Patched resolve_loops: {0}".format(count_not_patched))
def resolve_opaque_mov_push(): PATTERNS = [ "BB 00 00 00 00", "BB 01 00 00 00", "BB 02 00 00 00", "BB 03 00 00 00", "BB 04 00 00 00", "BB 05 00 00 00", "BB 06 00 00 00", "BB 07 00 00 00", "BB 08 00 00 00", "BB 09 00 00 00", "6A ?? 5B" ] count_patched = 0 count_not_patched = 0 for pattern in PATTERNS: ea = 0 print(pattern) while ea != BADADDR: ea = ida_search.find_binary( ea, BADADDR, pattern, 16, SEARCH_NEXT | SEARCH_DOWN | SEARCH_CASE) ''' pattern: BB 00 00 00 00 .text:00406A83 BB 00 00 00 00 mov ebx, 0 .text:00406A88 66 01 D0 add ax, dx .text:00406A8B 81 F1 B5 15 00 00 xor ecx, 15B5h .text:00406A91 66 35 7A 13 xor ax, 137Ah .text:00406A95 85 DB test ebx, ebx .text:00406A97 74 14 jz short loc_406AAD Patched EBX > 0 pattern: BB 09 00 00 00 .text:00406BE3 BB 09 00 00 00 mov ebx, 9 .text:00406BE8 20 D1 and cl, dl .text:00406BEA 66 09 D6 or si, dx .text:00406BED 66 89 D6 mov si, dx .text:00406BF0 85 DB test ebx, ebx .text:00406BF2 74 02 jz short loc_406BF6 Patched EBX == 0 .text:00406A83 90 nop .text:00406A84 90 nop .text:00406A85 90 nop .text:00406A86 90 nop .text:00406A87 90 nop .text:00406A88 90 nop .text:00406A89 90 nop .text:00406A8A 90 nop .text:00406A8B 90 nop .text:00406A8C 90 nop .text:00406A8D 90 nop .text:00406A8E 90 nop .text:00406A8F 90 nop .text:00406A90 90 nop .text:00406A91 90 nop .text:00406A92 90 nop .text:00406A93 90 nop .text:00406A94 90 nop .text:00406A95 90 nop .text:00406A96 90 nop .text:00406A97 EB 14 jmp short loc_406AAD ''' if ea_in_bounds(ea): ''' .text:00406A83 BB 00 00 00 00 mov ebx, <0-9> <- ebx_value .text:00406A88 66 01 D0 add ax, dx .text:00406A8B 81 F1 B5 15 00 00 xor ecx, 15B5h .text:00406A91 66 35 7A 13 xor ax, 137Ah .text:00406A95 85 DB test ebx, ebx .text:00406A97 74 14 jz short loc_406AAD ''' original_ea = ea ebx_value = Byte(ea + 1) instr = ida_ua.decode_insn(ea) if instr: has_test = False # while not jmp related instruction found while ((instr.itype <= idaapi.NN_ja) or (instr.itype >= idaapi.NN_jmpshort)): # move to next instr ea = ea + instr.size instr = ida_ua.decode_insn(ea) # Check in order to validate that has test func and is candidate to be patched if instr.itype == idaapi.NN_test: has_test = True # at this point "ea" variable contains the last instruction address # that is the conditional jump found if has_test: if instr.itype == idaapi.NN_jz: # ebx_value > 0 and NN_jz -> Patch with NOPs if ebx_value > 0: ''' .text:00406BE3 BB 09 00 00 00 mov ebx, 9 .text:00406BE8 20 D1 and cl, dl .text:00406BEA 66 09 D6 or si, dx .text:00406BED 66 89 D6 mov si, dx .text:00406BF0 85 DB test ebx, ebx .text:00406BF2 74 02 jz short loc_406BF6 ''' relative_offset = ea - original_ea # Patch the complete function number_nops = ea - original_ea + instr.size for i in range(0, number_nops): ida_bytes.patch_byte(original_ea + i, 0x90) idc.create_insn(ea) # ebx_value = 0 and NN_jz -> Patch with JMP else: ''' .text:00406A83 BB 00 00 00 00 mov ebx, 0 .text:00406A88 66 01 D0 add ax, dx .text:00406A8B 81 F1 B5 15 00 00 xor ecx, 15B5h .text:00406A91 66 35 7A 13 xor ax, 137Ah .text:00406A95 85 DB test ebx, ebx .text:00406A97 74 14 jz short loc_406AAD ''' # ea contains the conditional jmp address relative_offset = Byte(ea + 1) # NOP number_nops = ea - original_ea + instr.size for i in range(0, number_nops): ida_bytes.patch_byte(original_ea + i, 0x90) # Patch the conditional jmp to unconditional jmp ida_bytes.patch_byte(ea, 0xEB) ida_bytes.patch_byte(ea + 1, relative_offset) idc.create_insn(ea) count_patched += 1 else: count_not_patched += 1 print("\tPatched resolve_opaque_mov_push: {0}".format(count_patched)) print( "\tNot Patched resolve_opaque_mov_push: {0}".format(count_not_patched))