If someone feels the need to do this programmatically in a non-debug setting, this is the code I would use.
Note #1: There are some shortcut functions of my own GetFuncStart
and such that are not included, I'm sure you can work them out. The reference to the Commenter
class will just have to be removed.
Note #2: The seemingly extreme measures taken in ZeroFunction
are the only way I have found to ensure a function is actually rebuilt. None of the ida_auto functions seem to do much. You may opt for something less extreme if it suits you.
Note #3: The order of called functions is reversed to attempt to do the rebuild from the bottom up, hopefully allowing IDA to make more educated guesses about arguments and such.
Note #4: The many superfluous variables, lists and such are from the larger original function which (amongst other things) produces .dot vizgraphs. I have left them in lest someone find a use for them.
def RebuildFuncAndSubs(ea):
subs = RecurseCalled(ea)['calledLocs']
subs.reverse()
for addr in subs:
ZeroFunction(addr)
idc.auto_wait()
ZeroFunction(ea)
idc.auto_wait()
def RecurseCalled(ea=None, width=512):
def all_xrefs_from(funcea, iteratee=None):
if iteratee is None:
iteratee = lambda x: x
xrefs = []
for (startea, endea) in Chunks(funcea):
for head in Heads(startea, endea):
xrefs.extend([GetFuncStart(x.to) for x in idautils.XrefsFrom(head) \
if x.type in (ida_xref.fl_CF, ida_xref.fl_CN, ida_xref.fl_JF, ida_xref.fl_JN) \
and not ida_funcs.is_same_func(funcea, x.to) \
and IsFunc_(x.to)])
xrefs = list(set(xrefs))
return xrefs
if ea is None:
ea = idc.get_screen_ea()
calledNames = list()
calledLocs = list()
visited = set([])
if isinstance(ea, list):
pending = set(ea)
initial = set([GetFuncStart(x) for x in ea])
else:
pending = set([ea])
initial = set([GetFuncStart(ea)])
depth = 0
count = 0
added = [1]
functionCalls = collections.defaultdict(set)
namedFunctionCalls = collections.defaultdict(set)
fwd = dict()
rev = dict()
while pending and len(pending) < width:
target = pending.pop()
count += 1
added[0] -= 1
if added[0] < 1:
depth += 1
added.pop()
visited.add(target)
fnName = idc.get_func_name(target) or idc.get_name(target) or "0x%x" % ref
fnStart = GetFuncStart(target)
if fnStart < idc.BADADDR:
target = fnStart
visited.add(target)
if not fnStart in initial:
calledNames.append(fnName)
calledLocs.append(fnStart)
refs = all_xrefs_from(fnStart)
refs = set(refs)
refs -= visited
size1 = len(pending)
pending |= refs
size2 = len(pending) - size1
added.append(size2)
return {'calledName': calledNames,
'calledLocs': calledLocs,
}
def ZeroFunction(ea, total=False):
print("0x%x: ZeroFunction" % ea)
start = 0
end = 0
# Don't hold the func_t object open
func = clone_items(ida_funcs.get_func(ea))
if not func:
return BADADDR
# Keep existing comments
with Commenter(ea, 'func') as commenter:
fnLoc = func.start_ea
fnName = ida_funcs.get_func_name(fnLoc)
flags = func.flags # idc.get_func_attr(ea, FUNCATTR_FLAGS)
# remove library flag
idc.set_func_attr(fnLoc, FUNCATTR_FLAGS, flags & ~4)
ida_name.del_local_name(fnLoc)
ida_name.del_global_name(fnLoc)
# RemoveAllChunks(ea)
for start, end in idautils.Chunks(ea):
idc.remove_fchunk(start, end)
ida_funcs.del_func(func.start_ea)
idc.set_color(fnLoc, CIC_FUNC, 0xffffffff)
if not total:
func = ida_funcs.func_t(fnLoc)
res = ida_funcs.find_func_bounds(func, ida_funcs.FIND_FUNC_DEFINE | ida_funcs.FIND_FUNC_IGNOREFN)
if res == ida_funcs.FIND_FUNC_UNDEF:
print("0x%x ZeroFunction: func passed flow to unexplored bytes" % fnLoc)
elif res == ida_funcs.FIND_FUNC_OK:
ida_funcs.add_func_ex(func)
idc.auto_wait()
# remove library flag (again)
idc.set_func_flags(fnLoc, idc.get_func_flags(fnLoc) & ~4)
# return original function name
idc.set_name(fnLoc, fnName, idc.SN_NOWARN)