182 lines
6.3 KiB
Python
182 lines
6.3 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Dict, Iterable, List, Optional, Tuple
|
|
|
|
from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM
|
|
from capstone.arm64 import ARM64_OP_IMM
|
|
from elftools.elf.elffile import ELFFile
|
|
from elftools.elf.relocation import RelocationSection
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Symbol:
|
|
name: str
|
|
addr: int
|
|
size: int
|
|
bind: str
|
|
typ: str
|
|
|
|
|
|
class ElfAnalyzer:
|
|
def __init__(self, path: str | Path):
|
|
self.path = Path(path)
|
|
self._fp = self.path.open("rb")
|
|
self.elf = ELFFile(self._fp)
|
|
self._symbols = self._collect_symbols()
|
|
self._symbols_by_name = {s.name: s for s in self._symbols}
|
|
self._symbols_by_addr = {s.addr: s for s in self._symbols if s.addr}
|
|
self._plt_symbols = self._collect_plt_symbols()
|
|
self._cs = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
|
|
self._cs.detail = True
|
|
|
|
def close(self) -> None:
|
|
self._fp.close()
|
|
|
|
def _collect_symbols(self) -> List[Symbol]:
|
|
out: List[Symbol] = []
|
|
for section in self.elf.iter_sections():
|
|
if section["sh_type"] not in ("SHT_SYMTAB", "SHT_DYNSYM"):
|
|
continue
|
|
for sym in section.iter_symbols():
|
|
name = sym.name
|
|
if not name:
|
|
continue
|
|
info = sym["st_info"]
|
|
out.append(
|
|
Symbol(
|
|
name=name,
|
|
addr=int(sym["st_value"]),
|
|
size=int(sym["st_size"]),
|
|
bind=str(info["bind"]),
|
|
typ=str(info["type"]),
|
|
)
|
|
)
|
|
out.sort(key=lambda s: (s.addr, s.name))
|
|
return out
|
|
|
|
def _collect_plt_symbols(self) -> Dict[int, str]:
|
|
# AArch64 ELF PLT layout: the resolver entry is 32 bytes, then one
|
|
# 16-byte entry per relocation in .rela.plt order.
|
|
plt = self.elf.get_section_by_name(".plt")
|
|
if plt is None:
|
|
return {}
|
|
out: Dict[int, str] = {}
|
|
plt_addr = int(plt["sh_addr"])
|
|
entry_addr = plt_addr + 32
|
|
for section in self.elf.iter_sections():
|
|
if not isinstance(section, RelocationSection):
|
|
continue
|
|
if section.name not in (".rela.plt", ".rela.plt.sec", ".rel.plt"):
|
|
continue
|
|
symtab = self.elf.get_section(section["sh_link"])
|
|
for index, rel in enumerate(section.iter_relocations()):
|
|
sym = symtab.get_symbol(rel["r_info_sym"])
|
|
if sym.name:
|
|
out[entry_addr + index * 16] = sym.name + "@plt"
|
|
return out
|
|
|
|
def symbols(self, pattern: Optional[str] = None) -> List[Symbol]:
|
|
if pattern is None:
|
|
return list(self._symbols)
|
|
rx = re.compile(pattern)
|
|
return [s for s in self._symbols if rx.search(s.name)]
|
|
|
|
def find_symbol(self, name: str) -> Optional[Symbol]:
|
|
return self._symbols_by_name.get(name)
|
|
|
|
def describe_addr(self, addr: int) -> str:
|
|
if addr in self._plt_symbols:
|
|
return self._plt_symbols[addr]
|
|
if addr in self._symbols_by_addr:
|
|
return self._symbols_by_addr[addr].name
|
|
best: Optional[Symbol] = None
|
|
for sym in self._symbols:
|
|
if sym.addr and sym.size and sym.addr <= addr < sym.addr + sym.size:
|
|
if best is None or sym.addr > best.addr:
|
|
best = sym
|
|
if best is not None:
|
|
return f"{best.name}+0x{addr - best.addr:x}"
|
|
return f"0x{addr:x}"
|
|
|
|
def vaddr_to_offset(self, addr: int) -> int:
|
|
for seg in self.elf.iter_segments():
|
|
if seg["p_type"] != "PT_LOAD":
|
|
continue
|
|
start = int(seg["p_vaddr"])
|
|
size = int(seg["p_memsz"])
|
|
if start <= addr < start + size:
|
|
return int(seg["p_offset"]) + (addr - start)
|
|
raise ValueError(f"virtual address 0x{addr:x} is not in a PT_LOAD segment")
|
|
|
|
def read_vaddr(self, addr: int, size: int) -> bytes:
|
|
off = self.vaddr_to_offset(addr)
|
|
self._fp.seek(off)
|
|
return self._fp.read(size)
|
|
|
|
def disassemble_symbol(self, name: str, max_bytes: Optional[int] = None) -> List[str]:
|
|
sym = self.find_symbol(name)
|
|
if sym is None:
|
|
raise KeyError(name)
|
|
if sym.size <= 0:
|
|
raise ValueError(f"symbol {name} has no size")
|
|
size = min(sym.size, max_bytes) if max_bytes else sym.size
|
|
code = self.read_vaddr(sym.addr, size)
|
|
lines = [f"{name} @ 0x{sym.addr:x} size=0x{sym.size:x}"]
|
|
for insn in self._cs.disasm(code, sym.addr):
|
|
comment = ""
|
|
if insn.mnemonic in {"b", "bl", "cbz", "cbnz", "tbz", "tbnz"}:
|
|
imm = self._first_imm(insn)
|
|
if imm is not None:
|
|
comment = f" ; {self.describe_addr(imm)}"
|
|
lines.append(f"0x{insn.address:08x}:\t{insn.mnemonic}\t{insn.op_str}{comment}")
|
|
return lines
|
|
|
|
@staticmethod
|
|
def _first_imm(insn) -> Optional[int]:
|
|
for op in insn.operands:
|
|
if op.type == ARM64_OP_IMM:
|
|
return int(op.imm)
|
|
return None
|
|
|
|
|
|
def _default_so() -> Path:
|
|
return (
|
|
Path.cwd()
|
|
/ "IR_Camera_SDK-1.0.1"
|
|
/ "android"
|
|
/ "android"
|
|
/ "lib"
|
|
/ "arm64-v8a"
|
|
/ "libcoresdk.so"
|
|
)
|
|
|
|
|
|
def main(argv: Optional[List[str]] = None) -> int:
|
|
parser = argparse.ArgumentParser(description="Small ARM64 ELF disassembler for libcoresdk.so")
|
|
parser.add_argument("--elf", default=str(_default_so()))
|
|
parser.add_argument("--symbol", action="append", default=[])
|
|
parser.add_argument("--grep-symbols", help="Regex for listing matching symbols")
|
|
parser.add_argument("--max-bytes", type=lambda s: int(s, 0), default=None)
|
|
args = parser.parse_args(argv)
|
|
|
|
analyzer = ElfAnalyzer(args.elf)
|
|
try:
|
|
if args.grep_symbols:
|
|
for sym in analyzer.symbols(args.grep_symbols):
|
|
print(f"0x{sym.addr:08x}\t0x{sym.size:x}\t{sym.typ}\t{sym.name}")
|
|
for name in args.symbol:
|
|
for line in analyzer.disassemble_symbol(name, args.max_bytes):
|
|
print(line)
|
|
print()
|
|
finally:
|
|
analyzer.close()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|