From ec210c019f2c20543ab83c95b828c129de816c2f Mon Sep 17 00:00:00 2001 From: KeelrLeek <156356974+KeelrLeek@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:34:33 -0500 Subject: [PATCH] re_lookup/re_disassemble show the calling convention read from the bytes --- harbor/reverse.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/harbor/reverse.py b/harbor/reverse.py index 4809682..45ed5f5 100644 --- a/harbor/reverse.py +++ b/harbor/reverse.py @@ -39,6 +39,7 @@ class Reverse: self.imports_path = Path(config["imports"]) if config.get("imports") else None self.annotations = Path(config["annotations"]) if config.get("annotations") else None self.src = Path(config["src"]) if config.get("src") else None + self.conventions_path = Path(config["conventions"]) if config.get("conventions") else None self._cache = {} # ----------------------------------------------------------------- loading (cached by file mtime) @@ -162,6 +163,26 @@ class Reverse: raise HarborError(f"{target!r} is neither an address nor a known name") return addr + def convention(self, addr): + """The calling convention read from the bytes: register inputs (a dataflow pass) and the `ret N` cleanup.""" + if not self.conventions_path or not self.conventions_path.exists(): + return None + module = self._cache.get("conventions_module") + if module is None: + import importlib.util + spec = importlib.util.spec_from_file_location("harbor_conventions", self.conventions_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + self._cache["conventions_module"] = module + func = self.function_at(addr) + if not func: + return None + result = module.analyse(self._bytes(), func[0], func[1]) + if not result: + return None + return {"summary": module.describe(result), "registers_in": result["registers_in"], + "ret_pops": result["ret_pops"]} + # ----------------------------------------------------------------- tools def read(self, addr, count): data = self._bytes() @@ -192,6 +213,10 @@ class Reverse: lines = [] if func and not max_bytes: lines.append(f"; {func[2] or 'unnamed'} 0x{func[0]:X}..0x{func[0] + func[1]:X} ({func[1]} bytes)") + conv = self.convention(func[0]) + if conv: + lines.append(f"; convention from the bytes: {conv['summary']}; registers in: " + f"{', '.join(conv['registers_in']) or 'none'}; ret pops {conv['ret_pops']}") for ins in md.disasm(code, start): note = self._annotate(ins.op_str, ins.mnemonic, start, start + size) lines.append(f"{ins.address:08X} {ins.mnemonic:7} {ins.op_str}{(' ; ' + note) if note else ''}") @@ -261,6 +286,9 @@ class Reverse: func = self.function_at(addr) if func: out["function"] = {"start": f"0x{func[0]:X}", "size": func[1], "name": func[2]} + conv = self.convention(func[0]) + if conv: + out["convention_from_bytes"] = conv for table, key in (("names.csv", "names_row"), ("functions.csv", "functions_row"), ("globals.csv", "globals_row")): row = self._csv(table).get(addr) if row: