diff options
Diffstat (limited to 'server/patch_server.py')
| -rw-r--r-- | server/patch_server.py | 161 |
1 files changed, 142 insertions, 19 deletions
diff --git a/server/patch_server.py b/server/patch_server.py index ee7600a..160b15b 100644 --- a/server/patch_server.py +++ b/server/patch_server.py @@ -1033,6 +1033,31 @@ def set_thumb( # discover that F5 does nothing. "db_64bit": bool(db64 and want)} +def _idatui_add_func(ea): + """add_func at ``ea``, falling back to an explicit end. + + ida_funcs.add_func(ea) asks IDA to find the end and on carved or + freshly-marked code it often can't, failing with no reason given.""" + import ida_bytes + import ida_funcs + import ida_segment + import idaapi + + if idaapi.get_func(ea) is not None: + return True + if ida_funcs.add_func(ea): + return True + seg = ida_segment.getseg(ea) + hi = seg.end_ea if seg else ea + end = ea + while end < hi and ida_bytes.is_code(ida_bytes.get_flags(end)): + nxt = ida_bytes.get_item_end(end) + if nxt <= end: + break + end = nxt + return bool(end > ea and ida_funcs.add_func(ea, end)) + + @tool @idasync def define_func_run( @@ -1061,28 +1086,126 @@ def define_func_run( if fn is not None and fn.start_ea == ea: return {"addr": hex(ea), "ok": True, "start": hex(fn.start_ea), "end": hex(fn.end_ea), "how": "existed"} - if ida_funcs.add_func(ea): - f = idaapi.get_func(ea) - return {"addr": hex(ea), "ok": True, "start": hex(f.start_ea), - "end": hex(f.end_ea), "how": "auto"} - - seg = ida_segment.getseg(ea) - hi = seg.end_ea if seg else ea - end = ea - while end < hi and ida_bytes.is_code(ida_bytes.get_flags(end)): - nxt = ida_bytes.get_item_end(end) - if nxt <= end: - break - end = nxt - if end <= ea: - return {"addr": hex(ea), "ok": False, - "error": "no code here to make a function from"} - if not ida_funcs.add_func(ea, end): + auto = ida_funcs.add_func(ea) + if not auto and not _idatui_add_func(ea): return {"addr": hex(ea), "ok": False, - "error": f"IDA refused a function at {ea:#x}..{end:#x}"} + "error": f"IDA refused a function at {ea:#x}"} f = idaapi.get_func(ea) + if f is None: + return {"addr": hex(ea), "ok": False, "error": "function did not stick"} return {"addr": hex(ea), "ok": True, "start": hex(f.start_ea), - "end": hex(f.end_ea), "how": "explicit-end"} + "end": hex(f.end_ea), "how": "auto" if auto else "explicit-end"} + +@tool +@idasync +def decomp_error( + addr: Annotated[str, "Address of the function that failed to decompile"], +) -> dict: + """Why Hex-Rays refused this function, in its own words. + + The plain decompile tool reports "Decompilation failed at 0x0" and drops the + reason, which is the only useful part. Hex-Rays fills in a hexrays_failure_t + saying things like "only 64-bit functions can be decompiled in the current + database" — that one is unfixable in place (the database's bitness is set at + load), so a user who can't see it has no way to know they must reload.""" + import ida_funcs + import ida_hexrays + import ida_ida + + try: + ea = parse_address(addr) + except Exception as e: + return {"addr": str(addr), "error": str(e)} + out = {"addr": hex(ea), "bitness": ida_ida.inf_get_app_bitness()} + fn = ida_funcs.get_func(ea) + if fn is None: + out["reason"] = "no function here" + return out + try: + if not ida_hexrays.init_hexrays_plugin(): + out["reason"] = "the decompiler is not available for this processor" + return out + hf = ida_hexrays.hexrays_failure_t() + cf = ida_hexrays.decompile_func(fn, hf) + if cf is not None: + out["reason"] = "" # it decompiles now + return out + out["reason"] = hf.desc() or f"error {hf.code}" + out["code"] = int(hf.code) + out["errea"] = hex(hf.errea) + except Exception as e: # noqa: BLE001 + out["reason"] = f"{type(e).__name__}: {e}" + return out + +@tool +@idasync +def thumb_scan( + start: Annotated[str, "Start of the range to scan for entry pointers"] = "", + end: Annotated[str, "Exclusive end of the range (default: 1KB from start)"] = "", + apply: Annotated[bool, "Mark the targets as Thumb and disassemble them"] = True, + limit: Annotated[int, "Max entries to act on"] = 512, +) -> dict: + """Find Thumb entry points from ODD pointers, e.g. a Cortex-M vector table. + + An ARM function pointer carries the mode in bit 0: odd means Thumb. A vector + table is therefore a list of Thumb entry points that IDA won't follow on a + headerless image, because nothing tells it those words are pointers at all. + + Being wrong here is expensive — marking a data word as code corrupts the + listing — so a word only counts when it is odd, lands inside a loaded + segment, and its target is EXECUTABLE and not already defined as data. The + even words in a vector table (the initial stack pointer) fail the first test, + which is the point.""" + import ida_bytes + import ida_funcs + import ida_idp + import ida_segment + import ida_segregs + import ida_ua + + seg0 = ida_segment.getseg(parse_address(start)) if start else None + if seg0 is None: + seg0 = ida_segment.getnseg(0) + if seg0 is None: + return {"error": "no segments", "found": [], "applied": 0} + try: + lo = parse_address(start) if start else seg0.start_ea + hi = parse_address(end) if end else min(lo + 0x400, seg0.end_ea) + except Exception as e: + return {"error": str(e), "found": [], "applied": 0} + + treg = ida_idp.str2reg("T") + found, applied = [], 0 + ea = lo + while ea + 4 <= hi and len(found) < limit: + w = ida_bytes.get_dword(ea) + ea += 4 + if not (w & 1): + continue # even: not a Thumb pointer + tgt = w & ~1 + seg = ida_segment.getseg(tgt) + if seg is None or not (seg.perm & ida_segment.SEGPERM_EXEC or seg.perm == 0): + continue # points outside the image, or at data + f = ida_bytes.get_flags(tgt) + if ida_bytes.is_data(f): + continue # already something else; don't fight it + rec = {"at": hex(ea - 4), "value": hex(w), "target": hex(tgt), + "was_code": bool(ida_bytes.is_code(f))} + found.append(rec) + if not apply: + continue + if treg is not None and treg >= 0: + ida_segregs.split_sreg_range(tgt, treg, 1, ida_segregs.SR_user) + if not ida_bytes.is_code(ida_bytes.get_flags(tgt)): + ida_bytes.del_items(tgt, 0, 2) + if ida_ua.create_insn(tgt) <= 0: + rec["decoded"] = False + continue + rec["decoded"] = True + rec["function"] = _idatui_add_func(tgt) + applied += 1 + return {"start": hex(lo), "end": hex(hi), "found": found, + "applied": applied, "n": len(found)} ''' SNIPPET = f"{BEGIN}\n{BODY.strip()}\n{END}\n" |
