"""Shared reverse-engineering helpers for the MAG160C SDK binaries.""" import io import re import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1] / ".tools" / "python-revlibs")) from elftools.elf.elffile import ELFFile from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM, CS_ARCH_X86, CS_MODE_64 LIBCORESDK = r"C:\Project\MAG160C\IR_Camera_SDK-1.0.1\android\android\lib\arm64-v8a\libcoresdk.so" LIBMAGCORE = r"C:\Project\MAG160C\IR_Camera_SDK-1.0.1\linux\1.2.2\1.2.2\lib\libmagcore.so.2.1.1" class Loader: def __init__(self, path): self.path = Path(path) self._data = path_bytes = Path(path).read_bytes() self.elf = ELFFile(io.BytesIO(path_bytes)) self.arch = self.elf["e_machine"] self.isa64 = self.elf.elfclass == 64 if self.arch == "EM_AARCH64": self.md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) self.md.detail = True else: self.md = Cs(CS_ARCH_X86, CS_MODE_64) self.md.detail = True self._syms = None @property def syms(self): if self._syms is None: self._syms = [] for sec in self.elf.iter_sections(): if sec["sh_type"] in ("SHT_SYMTAB", "SHT_DYNSYM"): for s in sec.iter_symbols(): self._syms.append(s) return self._syms def find_sym(self, name, demangled=True): out = [] for s in self.syms: n = s.name if demangled: d = demangle(n) if d is not None: n = d if n == name: out.append(s) return out def vaddr_to_offset(self, va): for sec in self.elf.iter_sections(): addr, size = sec["sh_addr"], sec["sh_size"] if size and addr <= va < addr + size: return sec["sh_offset"] + (va - addr) return None def bytes_at(self, va, n): off = self.vaddr_to_offset(va) if off is None: return None return self._data[off:off + n] def read_u32(self, va): b = self.bytes_at(va, 4) return int.from_bytes(b, "little") if b and len(b) == 4 else None def read_ptr(self, va): n = 8 if self.isa64 else 4 b = self.bytes_at(va, n) return int.from_bytes(b, "little") if b and len(b) == n else None def cstr_at(self, va, maxlen=256): b = self.bytes_at(va, maxlen) if b is None: return None end = b.find(b"\x00") if end < 0: return None return b[:end].decode("latin-1", "replace") def disasm(self, va, count=None, stop_after_ret=8): """Linear-sweep disassemble starting at va; stop after N rets or count insns.""" lines = [] rets = 0 i = 0 cur = va while count is None or i < count: b = self.bytes_at(cur, 16) if not b: break ins = next(self.md.disasm(b, cur), None) if ins is None: lines.append(f"{cur:012x}: ") cur += 4 continue lines.append(f"{ins.address:012x}: {ins.mnemonic:<8s} {ins.op_str}") if ins.mnemonic.startswith("ret") or ins.mnemonic == "br" or (self.arch == "EM_AARCH64" and ins.mnemonic == "ret"): rets += 1 if stop_after_ret and rets >= stop_after_ret: break cur += ins.size i += 1 return lines def demangle(name): """Small C++ demangler covering the symbols we care about.""" if not name.startswith("_Z"): return None m = re.match(r"^_Z(T|V|N)?(.*)$", name) if not m: return None rest = m.group(2) if m.group(1) == "N": # N...E nested parts = [] i = 0 while i < len(rest) and rest[i] != "E": n = 0 j = i while rest[j].isdigit(): n = n * 10 + int(rest[j]) j += 1 if n == 0: return name parts.append(rest[j:j + n]) i = j + n i += 1 args = demangle_args(rest[i:]) return "::".join(parts) + ("(" + ", ".join(args) + ")" if args else "") # plain function _Z m2 = re.match(r"^(\d+)([A-Za-z_][A-Za-z0-9_]*)(.*)$", rest) if not m2: return name n = int(m2.group(1)) fn = m2.group(2)[:n] args = demangle_args(m2.group(3)) return fn + ("(" + ", ".join(args) + ")" if args else "") _ARG_TYPES = { "i": "int", "j": "uint", "t": "ushort", "k": "uint", "l": "ulong", "P": "void*", "PK": "const void*", "E": "struct", "S": "size_t", } def demangle_args(s): args = [] i = 0 while i < len(s) and s[i] != "E": c = s[i] if c in "ijtl": args.append({"i": "int", "j": "uint", "t": "ushort", "l": "ulong"}[c]) i += 1 elif c == "P": args.append("void*") i += 1 elif c == "K": args.append("const void*") i += 1 else: m = re.match(r"^(\d+)([A-Za-z_][A-Za-z0-9_]*)(.*)$", s[i:]) if not m: args.append("?") i += 1 continue n = int(m.group(1)) args.append(m.group(2)[:n]) i += len(m.group(1)) + n return args def resolve_plt(ld, va, depth=8): """Follow ARM64 PLT stub chains to final function address.""" seen = set() cur = va for _ in range(depth): if cur in seen: break seen.add(cur) b = ld.bytes_at(cur, 16) if not b: return None # PLT stub: adrp x16, #p; ldr x17, [x16, #o]; add x16, x16, #o; br x17 if len(b) >= 16: w0 = int.from_bytes(b[0:4], "little") w1 = int.from_bytes(b[4:8], "little") w2 = int.from_bytes(b[8:12], "little") w3 = int.from_bytes(b[12:16], "little") if (w0 & 0xFF000000) == 0x90000000 and (w1 & 0xFFC00000) == 0xF9400000: imm = ((w0 >> 29) & 0x3) | ((w0 >> 3) & 0xFFFFC) if w0 & 0x400000: imm |= ~0xFFFFF & 0xFFFFFFFF imm <<= 12 page = (cur + imm) & ~0xFFF off = ((w1 >> 10) & 0xFFF) * 8 got = page + off target = ld.read_ptr(got) if target is None: return None cur = target continue # direct branch (b/bl) if (b[0] & 0xFC) == 0x14: w = int.from_bytes(b[0:4], "little") imm = (w & 0x3FFFFFF) << 2 if w & 0x2000000: imm |= ~0x7FFFFFF & 0xFFFFFFFF return (cur + imm) & 0xFFFFFFFF return cur return None def disasm_func(path, symbol, count=None, stop_after_ret=8): ld = Loader(path) syms = ld.find_sym(symbol) if not syms: print(f"NOT FOUND: {symbol}") return for s in syms: if s["st_info"]["type"] != "STT_FUNC": continue print(f"== {symbol} @ {s['st_value']:x} size={s['st_size']} ==") for line in ld.disasm(s["st_value"], count=count, stop_after_ret=stop_after_ret): print(line) def find_xrefs(path, target_va): """Find direct branches/calls to target_va in .text via capstone linear sweep.""" ld = Loader(path) hits = [] for sec in ld.elf.iter_sections(): if sec["sh_type"] != "SHT_PROGBITS" or not (sec["sh_flags"] & 0x4): continue va = sec["sh_addr"] data = sec.data() cur = va while cur < va + len(data): ins = next(ld.md.disasm(data[cur - va:], cur), None) if ins is None: break if "call" in ins.mnemonic or "b" == ins.mnemonic or ins.mnemonic.startswith("b."): for op in ins.operands: if op.type == 2 and op.imm == target_va: # CS_OP_IMM hits.append((ins.address, ins.mnemonic, ins.op_str)) cur += ins.size return hits if __name__ == "__main__": import argparse ap = argparse.ArgumentParser() ap.add_argument("--so", default=LIBCORESDK) ap.add_argument("--sym") ap.add_argument("--addr", type=lambda x: int(x, 0)) ap.add_argument("--count", type=int) ap.add_argument("--ret", type=int, default=8) ap.add_argument("--xrefs", action="store_true", help="find xrefs to --addr") args = ap.parse_args() if args.xrefs: for a, m, o in find_xrefs(args.so, args.addr): print(f"{a:012x}: {m} {o}") elif args.addr is not None: for line in Loader(args.so).disasm(args.addr, count=args.count, stop_after_ret=args.ret): print(line) else: disasm_func(args.so, args.sym, count=args.count, stop_after_ret=args.ret)