建立 MAG160C 逆向工程交接仓库
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,181 @@
|
||||
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())
|
||||
@@ -0,0 +1,43 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
|
||||
class ElfAarch64Tests(unittest.TestCase):
|
||||
def test_can_find_exported_mag_symbol(self):
|
||||
import elf_aarch64
|
||||
|
||||
so_path = os.path.join(
|
||||
os.getcwd(),
|
||||
"IR_Camera_SDK-1.0.1",
|
||||
"android",
|
||||
"android",
|
||||
"lib",
|
||||
"arm64-v8a",
|
||||
"libcoresdk.so",
|
||||
)
|
||||
analyzer = elf_aarch64.ElfAnalyzer(so_path)
|
||||
sym = analyzer.find_symbol("MAG_LinkCamera")
|
||||
self.assertIsNotNone(sym)
|
||||
self.assertGreater(sym.size, 0)
|
||||
self.assertGreater(sym.addr, 0)
|
||||
|
||||
def test_disassembles_aarch64_function_and_resolves_calls(self):
|
||||
import elf_aarch64
|
||||
|
||||
so_path = os.path.join(
|
||||
os.getcwd(),
|
||||
"IR_Camera_SDK-1.0.1",
|
||||
"android",
|
||||
"android",
|
||||
"lib",
|
||||
"arm64-v8a",
|
||||
"libcoresdk.so",
|
||||
)
|
||||
analyzer = elf_aarch64.ElfAnalyzer(so_path)
|
||||
lines = analyzer.disassemble_symbol("MAG_LinkCamera")
|
||||
self.assertTrue(any("MAG_LinkCamera" in line for line in lines[:2]))
|
||||
self.assertTrue(any("\tbl" in line or "\tblr" in line for line in lines))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user