56 lines
2.0 KiB
Python
56 lines
2.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Scan firmware for command-dispatch CMP-immediate and branch-switch patterns."""
|
|
from capstone import Cs, CS_ARCH_ARM, CS_MODE_THUMB, CS_MODE_LITTLE_ENDIAN
|
|
import struct, collections
|
|
|
|
IMG = "app/firmware-PP_da14585_4.2_CH.img"
|
|
BASE = 0x07FC0000
|
|
HDR = 0x40
|
|
CODE_END = 0x119e4 # code region size
|
|
|
|
CMDS = {0xdd:"time", 0xe2:"commit", 0x03:"img_blk_full", 0x04:"img_blk_small",
|
|
0x01:"refresh", 0xAA:"refresh_small", 0xEF:"activate", 0xE6:"lut_cal",
|
|
0xFB:"sleep_time", 0xEA:"slot", 0xFA:"countdown_off", 0xE1:"mode",
|
|
0xE3:"invert", 0xE4:"font", 0xE5:"rotate", 0x23:"bw_3color", 0x14:"12_24h",
|
|
0x15:"clk_color", 0x99:"cal_text", 0x00:"refresh_step1", 0xE9:"carousel"}
|
|
|
|
def main():
|
|
data = open(IMG, "rb").read()[HDR:HDR+CODE_END]
|
|
md = Cs(CS_ARCH_ARM, CS_MODE_THUMB | CS_MODE_LITTLE_ENDIAN)
|
|
# collect all instructions via same sweep
|
|
insns = []
|
|
off = 0
|
|
while off < len(data):
|
|
chunk = data[off:off+0x2000]
|
|
addr = BASE + off
|
|
consumed = 0
|
|
for ins in md.disasm(chunk, addr):
|
|
if ins.address < addr + consumed:
|
|
continue
|
|
gap = ins.address - (addr + consumed)
|
|
if gap:
|
|
consumed += (gap // 2) * 2
|
|
insns.append(ins)
|
|
consumed = ins.address + ins.size - addr
|
|
off += consumed if consumed else 2
|
|
|
|
# CMP Rn, #imm with imm in CMDS
|
|
hits = collections.defaultdict(list)
|
|
for ins in insns:
|
|
if ins.mnemonic == "cmp":
|
|
parts = ins.op_str.split(",")
|
|
if len(parts) == 2 and parts[1].strip().startswith("#"):
|
|
try:
|
|
v = int(parts[1].strip()[1:], 0)
|
|
except ValueError:
|
|
continue
|
|
if v in CMDS:
|
|
hits[v].append((ins.address, ins.op_str))
|
|
for v in sorted(hits):
|
|
print(f"--- CMP #0x{v:02x} ({CMDS[v]}): {len(hits[v])} hits ---")
|
|
for a, ops in hits[v]:
|
|
print(f" 0x{a:08x}: cmp {ops}")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|