271 lines
8.8 KiB
Python
271 lines
8.8 KiB
Python
"""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}: <unresolvable>")
|
|
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<N><name><args>
|
|
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)
|