def rename(self, name=None): """ Renames (and comments) the string variable in IDA. :param str name: New name to given encoded string. (defaults to decoded_string) """ name = name or self.display_name if not name: append_debug( 'Unable to rename encoded string due to no decoded string: {!r}'.format(self), log_token='[!]') return # Add comment comment = '"{}"'.format(name[:self._MAX_COMMENT_LENGTH]) if len(name) > self._MAX_COMMENT_LENGTH: comment += ' (truncated)' if self.string_location not in (INVALID, UNUSED): idc.MakeRptCmt(self.string_location, comment) if self.string_reference not in (INVALID, UNUSED): idc.MakeRptCmt(self.string_reference, comment) # Set variable name if self.string_location not in (INVALID, UNUSED): idaapi.do_name_anyway(self.string_location, name[:self._MAX_NAME_LENGTH])
def annotate_code(self, enabled): for addr, infos in self.results.items(): if not enabled: status = to_status_name(infos.status) idc.MakeRptCmt(addr, status) else: idc.MakeRptCmt(addr, "") self.actions[self.ANNOT_CODE] = (self.annotate_code, not enabled) self.result_widget.action_selector_changed(self.ANNOT_CODE)
def label_and_fix_branch_islands(dsc_file, adrfind, jmp_to_code): """ labels, comments and fixes code flow on branch islands """ jmpaddrs = sorted(set(jmp_to_code.keys())) dsc_file.seek(0) header = dsc_header(dsc_file) dsc_file.seek(header.images_offset) i = 0 jmpaddrslen = len(jmpaddrs) for addr in jmpaddrs: print "status: 0x%X %d/%d" % (addr, i, jmpaddrslen) res = adrfind.find(addr) if not res: print "[!] coudln't find addr for addr:", addr dylib_path, dsc_offset, macho_offset = res exportname = adrfind.get_export_name_for_addr(addr) if _IN_IDA: eas = jmp_to_code[addr] for ea in eas: idc.MakeRptCmt(ea, "%s'%s" % (dylib_path, exportname)) if "branch_islands" in idc.SegName(ea): make_name(ea, exportname) # patch them to "RET" so they would return memcpy(ea, "\xC0\x03\x5F\xD6") make_islands_xrefs_force_bl_call(ea) else: print "[+] \\\\ %s" % exportname i += 1
def annotate_code(self, enabled): for ret_data in self.results: addr = ret_data.addr if not enabled: #Set the comment status_s = ret_data.get_status() labels_s = ''.join(["[%s]" % x for x in ret_data.get_labels()]) comment = "Status:%s %s" % (status_s, labels_s) if ret_data.is_tampering(): comment += ' Ret:%s' % str( ["%x" % x for x in ret_data.returnsites]) idc.MakeRptCmt(addr, comment) else: #Remove the comment idc.MakeRptCmt(addr, "") self.actions[self.ANNOT_CODE] = (self.annotate_code, not (enabled)) self.result_widget.action_selector_changed(self.ANNOT_CODE)
def append_comment(ea, s, repeatable=False): """ add the given string as a (possibly repeating) comment to the given address. does not add the comment if it already exists. adds the comment on its own line. Args: ea (int): the address at which to add the comment. s (str): the comment text. repeatable (bool): if True, set a repeatable comment. Raises: UnicodeEncodeError: if the given string is not ascii. """ # see: http://blogs.norman.com/2011/security-research/improving-ida-analysis-of-x64-exception-handling s = s.encode("ascii") if repeatable: string = idc.RptCmt(ea) else: string = idc.Comment(ea) if not string: string = s # no existing comment else: if s in string: # ignore duplicates return string = string + "\\n" + s if repeatable: idc.MakeRptCmt(ea, string) else: idc.MakeComm(ea, string)
def yatest_comments(self): eas = [] for offset in range(0, 3): for fn_cmt, fn_rpt, cmt, rpt, post, ant in tests: ea = get_func_item(offset) eas.append(ea) logger.debug("setting at 0x%08X : %r, %r, %r, %r, %r, %r" % (ea, fn_cmt, fn_rpt, cmt, rpt, post, ant)) if fn_cmt != None: self.assertEqual(idc.SetFunctionCmt(ea, fn_cmt, False), True) if fn_rpt != None: self.assertEqual(idc.SetFunctionCmt(ea, fn_rpt, True), True) if cmt != None: self.assertEqual(idc.MakeComm(ea, cmt), True) if rpt != None: self.assertEqual(idc.MakeRptCmt(ea, rpt), True) if post != None: for i, txt in enumerate(post.split('\n')): self.try_ext_lin(idc.ExtLinB, ea, i, txt) if ant != None: for i, txt in enumerate(ant.split('\n')): self.try_ext_lin(idc.ExtLinA, ea, i, txt) yaunit.save('comments', eas)
def rename(self, name=None): """ Renames (and comments) the string variable in IDA. :param str name: New name to given encoded string. (defaults to decoded_string) """ name = name or self.display_name if not name: append_debug( 'Unable to rename encoded string due to no decoded string: {!r}'.format(self), log_token='[!]') # Set name and comment in stack variable. comment = '"{}"'.format(name[:self._MAX_COMMENT_LENGTH]) if len(name) > self._MAX_COMMENT_LENGTH: comment += ' (truncated)' if self.frame_id and self.stack_offset: idc.SetMemberComment(self.frame_id, self.stack_offset, comment, repeatable=1) var_name = re.sub('[^_$?@0-9A-Za-z]', '_', name[:self._MAX_NAME_LENGTH]) # Replace invalid characters if not var_name: raise ValueError('Unable to calculate var_name for : {!r}'.format(self)) var_name = 'a' + var_name.capitalize() idc.SetMemberName(self.frame_id, self.stack_offset, var_name) # Add a comment where the string is being used. if self.string_reference: idc.MakeRptCmt(self.string_reference, comment)
def execute_comment(comment): """ XXX : switch on the comment type """ print comment["address"] idc.MakeRptCmt(comment["address"], comment["data"].encode('ascii', 'replace')) print "[x] Added comment %s @0x%x " % (comment["data"], comment["address"])
def on_pre_update(cls, patch_type, name, address, data): added_comment = str(cls.comment_format % (name, address)) if idc.RptCmt(address) is not None and added_comment in idc.RptCmt( address): comment = idc.RptCmt(address).replace("\n" + added_comment, "").replace( added_comment, "") idc.MakeRptCmt(address, comment)
def on_perform_post_operations(cls, patch_type, name, address, data): original = idc.RptCmt(address) if original is not None: prefix = original + "\n" else: prefix = "" idc.MakeRptCmt(address, str(prefix + cls.comment_format % (name, address)))
def execute_comment(comment): """ XXX : switch on the comment type """ idc.MakeRptCmt(comment["address"], comment["data"].encode('ascii', 'replace')) g_logger.debug("[x] Added comment %s @0x%x " % (comment["data"], comment["address"]))
def main(): print('[+] start') currer = idc.ScreenEA() print('[+] address: %#x' % currer) crypted_data, crypted_size = get_data(currer) if crypted_size > 0: decode_data = decode(crypted_data, crypted_size) idc.MakeRptCmt(currer, str(decode_data)) else: print('[-] failed')
def done(addr): print('[+] address: %#x' % addr) crypted_data, crypted_size = get_data(addr) if crypted_size > 0: decode_data = decode(crypted_data, crypted_size) idc.MakeRptCmt(addr, str(decode_data)) return decode_data #print('[+] size: %#x' % crypted_size) else: print('[-] failed') return None
def annotate_code(self, enabled): if not enabled: #Annotate s = ":[" + self.results.get_status() + "]" if self.results.has_values(): s += " vals:[" + ''.join( ["%x," % x for x in self.results.values])[:-1] + "]" cmt = idc.RptCmt(self.results.target) if cmt != "": self.backup_comment[self.results.target] = cmt if cmt.startswith("//@assert"): s = cmt + s else: s = cmt + "\n" + self.results.query + s else: s = self.results.query + s self.backup_comment[self.results.target] = "" idc.MakeRptCmt(self.results.target, s.encode("utf-8", "ignore")) else: for addr, cmt in self.backup_comment.items(): idc.MakeRptCmt(addr, cmt) self.backup_comment.clear() self.actions[self.ANNOT_CODE] = (self.annotate_code, not (enabled)) self.result_widget.action_selector_changed(self.ANNOT_CODE)
def add_fct_descr(ea, function, rep): """ Insert a (repeatable) comment describing the function at ea. Arguments: ea -- effective address where the comment is added function -- function object holding data rep -- add repeatable comment (True/False) """ descr = format_comment(function.description) + '\n' + \ format_comment('RETURN VALUE: ' + function.returns) # Both functions do not return if rep: idc.MakeRptCmt(ea, descr) else: idc.MakeComm(ea, descr)
def AppendComment(ea, s, repeatable=False): # see williutils and http://blogs.norman.com/2011/security-research/improving-ida-analysis-of-x64-exception-handling if repeatable: string = idc.RptCmt(ea) else: string = idc.Comment(ea) if not string: string = s # no existing comment else: if s in string: # ignore duplicates return string = string + "\n" + s if repeatable: idc.MakeRptCmt(ea, string) else: idc.MakeComm(ea, string)
def import_comments(comments, sections): """Import BN comments """ for addr, current_function in comments.iteritems(): addr = base_addr_off_section(sections, int(addr)) if addr is None: continue if current_function["comment"]: idc.MakeRptCmt(int(addr), current_function["comment"].encode("utf-8")) for instr_addr, comment in current_function["comments"].iteritems(): instr_addr = base_addr_off_section(sections, int(instr_addr)) if instr_addr is None: continue idc.MakeComm(instr_addr, comment.encode("utf-8"))
def RetAddrStackWalk(nn, long_size): # get stack pointer if long_size == 8: sp = cpu.Rsp else: sp = cpu.Esp seg = idaapi.getseg(sp) if not seg: return (False, "Could not locate stack segment!") stack_seg_end = seg.endEA for sp in range(cpu.Esp, stack_seg_end + long_size, long_size): if long_size == 8: ptr = idc.Qword(sp) else: ptr = idc.Dword(sp) seg = idaapi.getseg(ptr) # only accept executable segments if (not seg) or ((seg.perm & idaapi.SEGPERM_EXEC) == 0): continue # try to find caller caller = IsPrevInsnCall(ptr) # we have no recognized caller, skip! if not caller: continue # do we have a debug name that is near? if nn: near = nn.find(caller) if near: # function exists? f = idaapi.get_func(near[0]) if not f: # create function idc.MakeFunction(near[0], idaapi.BADADDR) # get the flags f = idc.GetFlags(caller) # no code there? if not isCode(f): MakeCode(caller) idc.SetColor(sp, idc.CIC_ITEM, 0xc7c7ff) idc.MakeRptCmt(sp, CreateCommentString(caller, sp))
def db_write(cls, address, key, value, repeatable=0): result = cls.db_read(address, repeatable=repeatable) result[key] = value if '__color__' in result: value = result['__color__'] cls.color(address, value) del(result['__color__']) if '__address__' in result: del(result['__address__']) # del all hidden things result = dict((k,v) for k,v in result.iteritems() if not k.startswith('__')) res = comment.toString(result).encode('ascii') if repeatable: return idc.MakeRptCmt(address, res) return idc.MakeComm(address, res)
def delete_comment_at_ea(ea, comment_type): logger.debug("Deleting comment at 0x%08X / %d" % (ea, comment_type)) if comment_type == ya.COMMENT_REPEATABLE: idc.MakeRptCmt(ea, "") # TODO: remove the test with "comment" (temporary fix because of cache incoherency) elif comment_type == ya.COMMENT_NON_REPEATABLE or comment_type == "comment": idc.MakeComm(ea, "") elif comment_type == ya.COMMENT_ANTERIOR: for i in xrange(0, idaapi.get_first_free_extra_cmtidx(ea, idaapi.E_PREV)): idaapi.del_extra_cmt(ea, idaapi.E_PREV + i) elif comment_type == ya.COMMENT_POSTERIOR: for i in xrange(0, idaapi.get_first_free_extra_cmtidx(ea, idaapi.E_NEXT)): idaapi.del_extra_cmt(ea, idaapi.E_NEXT + i) elif comment_type == ya.COMMENT_BOOKMARK: # parse marked position for i in xrange(1, 1024): if idc.GetMarkedPos(i) == idc.BADADDR: break elif idc.GetMarkedPos(i) == ea: idc.MarkPosition(ea, 0, 0, 0, i, "")
def yatest_data_comments(self): eas = [] for offset in range(0, 3): for cmt, rpt, post, ant in tests_data: ea = get_data_item() eas.append(ea) logger.debug( "setting data comment at 0x%08X : %r, %r, %r, %r" % (ea, cmt, rpt, post, ant)) if cmt != None: idc.MakeComm(ea, cmt) if rpt != None: idc.MakeRptCmt(ea, rpt) if post != None: for i, txt in enumerate(post.split('\n')): try_ext_lin(idc.ExtLinB, ea, i, txt) if ant != None: for i, txt in enumerate(ant.split('\n')): try_ext_lin(idc.ExtLinA, ea, i, txt) yaunit.save('data_comments', eas)
def yatest_code_comments(self): eas = [] for offset in range(0, 3): for cmt, rpt, post, ant in tests_code: ea = get_code_item() eas.append(ea) logger.debug( "setting code comment at 0x%08X : %r, %r, %r, %r" % (ea, cmt, rpt, post, ant)) if cmt: self.assertEqual(idc.MakeComm(ea, cmt), True) if rpt: self.assertEqual(idc.MakeRptCmt(ea, rpt), True) if post: for i, txt in enumerate(post.split('\n')): self.try_ext_lin(idc.ExtLinB, ea, i, txt) if ant: for i, txt in enumerate(ant.split('\n')): self.try_ext_lin(idc.ExtLinA, ea, i, txt) yaunit.save('code_comments', eas)
def add_struc_descr(sid, structure, rep): """ Insert a (repeatable) comment descripting the structure whose id is sid. And name address in added segment annotated with structure description. Arguments: sid -- structure id which the added comment is describing structure -- structure object holding data rep -- add repeatable comment (True\False) Return: True -- if success; False otherwise """ # TODO correct or not descr = format_comment(structure.description) + '\n' if idc.SetStrucComment(sid, descr, rep): frm = [x.frm for x in idautils.XrefsTo(sid)] for ea in frm: # Added comment for global %structure.name% variable or pointer if ea > idc.MaxEA(): # getting 'member_t' using ea as 'mid' mptr = idaapi.get_member_by_id(ea) # IDA 6.8: setting member comment using 'mptr' as index idaapi.set_member_cmt(mptr, descr, rep) # IDA 6.9: mptr is type of list #idaapi.set_member_cmt(mptr[0], descr, rep) else: if not rep: idc.MakeComm(ea, descr) else: idc.MakeRptCmt(ea, descr) return True else: return False
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 = track_param(ea, func_start, idc.o_displ, sel_param) if method_name_ea: method_name = idc.GetString(method_name_ea, -1, idc.ASCSTR_C) if not method_name: method_name = '' else: method_name = '' class_name_ea = track_param(ea, func_start, idc.o_phrase, class_param) if class_name_ea: class_name = idc.GetString(class_name_ea, -1, idc.ASCSTR_C) if not class_name: class_name = '' else: class_name = '' if not method_name and not class_name: 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_name_', '') new_name = '_[' + class_name + '_' + method_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) & idc.BADADDR else: # create code and name it idc.PatchDword(seg_ptr, 0x90) # nop 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 dw = offset - 5 idc.PatchByte(ea, 0xE8) idc.PatchDword(ea + 1, dw)
def repeat(self, comment): idc.MakeRptCmt(self._ea, comment)
def make_rpt(): idc.MakeRptCmt(comment["address"], comment["data"].encode('ascii', 'replace'))
def makeRptComment(self, ea, comment): return idc.MakeRptCmt(ea, comment)
def dispatch(args): ## dispatch args.... class A(): pass a = A() for name in args: setattr(a, name, args[name]) del args ### dispatch idp events if a.action == 'rename': debug('[*] renaming %s to %s @ %x', a.old_name, a.new_name, a.ea) idc.MakeNameEx(a.ea, str(a.new_name), idaapi.SN_NOWARN) return ## dispatch idb events if a.action == 'cmt_changed': if not a.cmt: a.cmt = '' _pcmt = a.cmt if len(a.cmt) < 10 else a.cmt[:10] + '...' debug('[*] cmt changed @ %X (rep:%s) - %s', a.ea, a.rep, _pcmt) if a.rep: idc.MakeRptCmt(a.ea, str(a.cmt)) else: idc.MakeComm(a.ea, str(a.cmt)) if a.action == 'struct_created': debug('[*] Struct %s created with id %x', a.sname, a.struct) print idc.AddStrucEx(-1, str(a.sname), a.union) if a.action == 'struct_deleted': sid = idc.GetStrucId(a.struct) sname = idc.GetStrucName(sid) debug('[*] Struct(%x) %s deleted', a.struct, sname) idc.DelStruc(sid) if a.action == 'struct_renamed': sid = idc.GetStrucId(a.struct) sname = idc.GetStrucName(sid) debug('[*] Struct(%d - %x) renamed from %s to %s', a.struct, sid, sname, a.sname) idc.SetStrucName(sid, str(a.sname)) if a.action == 'struct_cmt_changed': sid = idc.GetStrucId(a.struct) debug('[*] Struct(%d - %x) %s - cmt changed', a.struct, sid, a.sname) idc.SetStrucComment(sid, a.cmt, 0) if a.action == 'struct_expanded': pass if a.action == 'struct_member_created': pass if a.action == 'struct_member_deleted': pass if a.action == 'struct_member_renamed': pass if a.action == 'struct_member_changed': pass if a.action == 'struct_member_': pass if a.action == 'struct_member_created': pass if a.action == 'struct_member_created': pass # if a.action == 'struct_expanded': # debug(' return 0
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 db_empty(cls, address, repeatable=0): if repeatable: cls.color(address, None) return idc.MakeRptCmt(int(address), '') cls.color(address, None) return idc.MakeComm(int(address), '')