def mem_hook(unicornObject, accessType, memAccessAddress, memAccessSize, memValue, userData): #if accessType == UC.UC_MEM_READ: # print("Read: ", hex(memAccessAddress), memAccessSize, hex(memValue)) if accessType == UC.UC_MEM_WRITE: #print("Write: ", hex(memAccessAddress), memAccessSize, hex(memValue)) if memAccessSize == 1: idc.PatchByte(memAccessAddress, memValue) elif memAccessSize == 2: idc.PatchWord(memAccessAddress, memValue) elif memAccessSize == 4: idc.PatchDword(memAccessAddress, memValue)
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: idc.PatchByte(ea, value) elif wordsize == 2: idc.PatchWord(ea, value) elif wordsize == 4: idc.PatchDword(ea, value) elif wordsize == 8: idc.PatchQword(ea, value) else: raise ValueError('Invalid argument: wordsize={}'.format(wordsize))
def fix_callgraph(msgsend, segname, class_param, sel_param): ''' fix_callgraph: msgsend, segname, class_param, sel_param Given the msgsend flavour address as a parameter, looks for the parameters (class and selector, identified by class_param and sel_param) and creates a new segment where it places a set of dummy calls named as classname_methodname (we use method instead of selector most of the time). ''' t1 = time.time() if not msgsend: print 'ERROR: msgSend not found' return total = 0 resolved = 0 call_table = dict() for xref in idautils.XrefsTo(msgsend, idaapi.XREF_ALL): total += 1 ea_call = xref.frm func_start = idc.GetFunctionAttr(ea_call, idc.FUNCATTR_START) if not func_start or func_start == idc.BADADDR: continue ea = ea_call method_name_ea = trace_param(ea, func_start, idc.o_reg, sel_param) if method_name_ea and idc.isASCII(idc.GetFlags(method_name_ea)): method_name = idc.GetString(method_name_ea, -1, idc.ASCSTR_C) if not method_name: method_name = '_unk_method' else: method_name = '_unk_method' class_name_ea = trace_param(ea, func_start, idc.o_reg, class_param) if class_name_ea: class_name = idc.Name(class_name_ea) if not class_name: class_name = '_unk_class' else: class_name = '_unk_class' if method_name == '_unk_method' and class_name == '_unk_class': continue # Using this name convention, if the class and method # are identified by IDA, the patched call will point to # the REAL call and not one of our dummy functions # class_name = class_name.replace('_OBJC_CLASS_$_', '') class_name = class_name.replace('_OBJC_METACLASS_$_', '') new_name = '_[' + class_name + '_' + method_name + ']' print '%08x: %s' % (ea_call, new_name) call_table[ea_call] = new_name resolved += 1 print '\nFinal stats:\n\t%d total calls, %d resolved' % (total, resolved) print '\tAnalysis took %.2f seconds' % (time.time() - t1) if resolved == 0: print 'Nothing to patch.' return print 'Adding new segment to store new nullsubs' # segment size = opcode ret (4 bytes) * num_calls seg_size = resolved * 4 seg_start = idc.MaxEA() + 4 idaapi.add_segm(0, seg_start, seg_start + seg_size, segname, 'CODE') print 'Patching database...' seg_ptr = seg_start for ea, new_name in call_table.items(): if idc.LocByName(new_name) != idc.BADADDR: offset = idc.LocByName(new_name) - ea else: # create code and name it idc.PatchDword(seg_ptr, 0xE12FFF1E) # BX LR idc.MakeName(seg_ptr, new_name) idc.MakeCode(seg_ptr) idc.MakeFunction(seg_ptr, seg_ptr + 4) idc.MakeRptCmt(seg_ptr, new_name) offset = seg_ptr - ea seg_ptr += 4 # patch the msgsend call if idc.GetReg(ea, "T") == 1: if offset > 0 and offset & 0xFF800000: print 'Offset too far for Thumb (%08x) Stopping [%08x]' % (offset, ea) return off1 = (offset & 0x7FF000) >> 12 off2 = (offset & 0xFFF) / 2 w1 = (0xF000 | off1) w2 = (0xE800 | off2) - 1 idc.PatchWord(ea, w1) idc.PatchWord(ea + 2, w2) else: if offset > 0 and offset & 0xFF000000: print 'Offset too far (%08x) Stopping [%08x]' % (offset, ea) dw = (0xFA000000 | (offset - 8 >> 2)) if dw < 0: dw = dw & 0xFAFFFFFF idc.PatchDword(ea, dw)
def obfuscated_jz_jnz(): ''' Resolve opaque predicates, in case that opaque predicate points to FF 25 the call will be patched: Example: .text:10001540 68 71 15 00 10 push offset byte_10001571 .text:10001545 0F 84 57 5C 03 00 jz loc_100371A2 .text:1000154B 0F 85 51 5C 03 00 jnz loc_100371A2 Pattern 68 ?? ?? ?? ?? ?? 0F 84 ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 68 ?? ?? ?? ?? 0F 84 ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? Operation -> DWORD( 9C 9F 01 00 ) - DWORD( 96 9F 01 00 ) == 6 That means that jumps to the same relative address 0x19f9C - 0x19F96 = 0x6 If that address points to FF 25 ?? ?? ?? ?? .text:100371A2 FF 25 9C 90 03 10 jmp ds:lstrlenA <---------- .text:100371A8 ----------------------------- .text:100371A8 FF 25 A0 90 03 10 jmp ds:GetModuleHandleA .text:100371AE ----------------------------- .text:100371AE FF 25 A4 90 03 10 jmp ds:LoadLibraryA .text:100371B4 ----------------------------- .text:100371B4 .text:100371B4 .text:100371B4 .text:100371B4 FF 25 A8 90 03 10 jmp ds:GetLastError .text:100371BA ----------------------------- .text:100371BA FF 25 AC 90 03 10 jmp ds:lstrcpyA Patch with CALL PUSH RET formula .text:10001540 90 nop ; Return addr - 0x10001571 .text:10001541 90 nop .text:10001542 90 nop .text:10001543 90 nop .text:10001544 90 nop .text:10001545 FF 15 9C 90 03 10 call ds:lstrlenA .text:1000154B 68 71 15 00 10 push 10001571h .text:10001550 C3 retn ''' count_patched = 0 count_not_patched = 0 ea = 0 while ea != BADADDR: ea = idc.FindBinary( ea, SEARCH_NEXT | SEARCH_DOWN | SEARCH_CASE, "68 ?? ?? ?? ?? 0F 84 ?? ?? ?? ?? 0F 85 ?? ?? ?? ??") if ea and segment_name == idc.SegName(ea): idc.MakeComm( ea, "Return addr - {0}".format(hex(Dword(ea + 0x1)).split("L")[0])) jz_pos = ea + 0x5 jz_value = Dword(jz_pos + 0x2) jnz_pos = jz_pos + 0x6 jnz_value = Dword(jnz_pos + 0x2) # Check same jmp addr if jz_value - jnz_value == 0x6: pos_jmp = jz_pos + jz_value + 0x6 # If the jmp points to a FF 25 instruction (absolute jmp) if Word(pos_jmp) == 0x25FF: ''' .text:1000153F 55 push ebp .text:10001540 68 71 15 00 10 push offset byte_10001571 .text:10001545 0F 84 57 5C 03 00 jz loc_100371A2 .text:1000154B 0F 85 51 5C 03 00 jnz loc_100371A2 .text:10001551 56 push esi ''' # Patch the conditional jmp, copying the absolute jmp into it idc.PatchWord(jz_pos, Word(pos_jmp)) idc.PatchDword(jz_pos + 0x2, Dword(pos_jmp + 0x2)) # At this point the FF 25 instruction have been copied into the conditional jmp position # Ex: FF 25 3C 70 48 71 jmp ds:CryptGenRandom # But the absolute jmp will be patched with a call FF 15 # FF 15 call idc.PatchByte(jz_pos, 0xFF) idc.PatchByte(jz_pos + 0x1, 0x15) # Copy the push instruction where conditonal jmp was idc.PatchWord(jnz_pos, Word(ea)) idc.PatchWord(jnz_pos + 0x1, Word(ea + 0x1)) idc.PatchWord(jnz_pos + 0x3, Word(ea + 0x3)) idc.PatchByte(jnz_pos + 0x5, 0xC3) # Nop the first 5 bytes. patch_loop(ea, 0x5, 0x90) ''' .text:10001540 90 nop ; Return addr - 0x10001571 .text:10001541 90 nop .text:10001542 90 nop .text:10001543 90 nop .text:10001544 90 nop .text:10001545 FF 15 9C 90 03 10 call ds:lstrlenA .text:1000154B 68 71 15 00 10 push 10001571h .text:10001550 C3 retn ''' idc.MakeCode(ea) else: ''' .text:10021C0C 68 27 1C 02 10 push offset loc_10021C27 .text:10021C11 0F 84 09 F9 FD FF jz loc_10001520 .text:10021C17 0F 85 03 F9 FD FF jnz loc_10001520 .text:10021C0C 90 nop .text:10021C0D 90 nop .text:10021C0E 90 nop .text:10021C0F 90 nop .text:10021C10 90 nop .text:10021C11 90 nop .text:10021C12 90 nop .text:10021C13 90 nop .text:10021C14 90 nop .text:10021C15 90 nop .text:10021C16 90 nop .text:10021C17 E9 04 F9 FD FF jmp loc_10001520 .text:10021C1C 90 nop <- last byte of old jnz instr ''' # The first 11 bytes to nop for i in range(0, 11): idc.PatchByte(ea + i, 0x90) # Add 1 to the address, because the size of the conditional jmp is 6, and the size of the unconditional jmp is 5 addr_to_jmp = Dword(jnz_pos + 0x2) + 0x1 # Patch first jz_value with unconditional jmp idc.PatchByte(jnz_pos, 0xE9) # Set the relative address idc.PatchDword(jnz_pos + 0x1, addr_to_jmp) # Last byte of the jnz to NOP (0x90) idc.PatchByte(jnz_pos + 0x5, 0x90) idc.MakeCode(ea) ''' .text:10021C0C 90 nop .text:10021C0D 90 nop .text:10021C0E 90 nop .text:10021C0F 90 nop .text:10021C10 90 nop .text:10021C11 90 nop .text:10021C12 90 nop .text:10021C13 90 nop .text:10021C14 90 nop .text:10021C15 90 nop .text:10021C16 90 nop .text:10021C17 E9 04 F9 FD FF jmp loc_10001520 .text:10021C1C 90 nop <- last byte of old jnz instr ''' count_patched += 1 else: count_not_patched += 1 print "\tPatched obfuscated_jz_jnz: {0}".format(count_patched) print "\tNot Patched obfuscated_jz_jnz: {0}".format(count_not_patched)
#coding=utf-8 import idc import idaapi import idautils idc.PatchByte(ea, value) #修改字节 idc.PatchWord(ea, value) #修改字 idc.PatchDword(ea, value) #修改双字 # 利用异或解密算法 解密选中的加密字符串 start = idc.SelStart() end = idc.SelEnd() print hex(start) print hex(end) def xor(size, key, buff): for index in range(0, size): cur_addr = buff + index temp = idc.Byte(cur_addr) ^ key idc.PatchByte(cur_addr, temp) xor(end - start, 0x30, start) print idc.GetString(start)