1.函数跳板-BL-BR

这里跳转到sub_12C6A4函数时候会将下一个指令也就是返回地址压入栈中,然后跳转过去,注意这个时候。函数内部是将x0也就是下一个地址返回,然后进行了加法跳转,形成了一个简单的间接跳转

简单计算然后nop

1.2

这里跳板函数的上下都用到了,所以在patch的时候要充分考虑每一种情况

# -*- coding: utf-8 -*-
"""
v5:批量清除 ARM64 "BL LR-gadget + ADD + BR" 花指令(完整版)

识别的花型(ARM64):
形式A: BL gadget ; MOV Ra, X0 ; ADD Ra, Ra, #imm ; BR Ra
形式B: BL gadget ; ADD Rd, X0, #imm ; BR Rd (无 MOV)
gadget: SUB SP,#0x10 ; STP X29,X30,[SP] ; LDR X0,[SP,#8] ; LDP ; ADD SP ; RET
(作用:把调用者的 LR 读出来返回)

处理: 从 BL 到真实目标(LR+imm)的整段填平为 B + NOP,
最后统一等待分析并重建函数。

用法:
1. 先 DRY_RUN=True 运行,检查输出中的目标地址是否合理
2. 改 DRY_RUN=False 重跑
3. Edit -> Patch program -> Apply patches to input file 保存
"""

import idaapi
import idautils
import ida_funcs
import ida_bytes
import ida_ua
import ida_segment
import ida_auto
import ida_xref

DRY_RUN = False # True=只报告不动手;False=执行 patch

NOP_BYTES = b"\x1F\x20\x03\xD5" # NOP (0xD503201F)

try:
from ida_idp import get_reg_name
except ImportError:
get_reg_name = idaapi.get_reg_name

# 操作数类型常量兼容 IDA8 / IDA9
O_REG = getattr(idaapi, "o_reg", ida_ua.o_reg)
O_IMM = getattr(idaapi, "o_imm", ida_ua.o_imm)


# ---------------- 编码 / 解码辅助 ----------------

def enc_b(pc, target):
"""AArch64 无条件跳转 B <label> 的机器码(4 字节,小端)"""
disp = target - pc
assert disp % 4 == 0, "跳转未对齐: 0x%X -> 0x%X" % (pc, target)
assert -0x2000000 <= disp < 0x2000000, "超出 ±128MB 范围"
word = 0x14000000 | ((disp >> 2) & 0x03FFFFFF)
return word.to_bytes(4, "little")

def dec(ea):
"""解码一条指令,失败返回 None"""
insn = ida_ua.insn_t()
return insn if ida_ua.decode_insn(insn, ea) > 0 else None

def mn(insn):
return insn.get_canon_mnem()

def regname(insn, i):
"""取第 i 个操作数的寄存器名(如 'X1'),非寄存器返回 None"""
op = insn.ops[i]
if op.type != O_REG:
return None
try:
return get_reg_name(op.reg, 8)
except Exception:
return None

def _add_imm(insn):
"""ADD Rd, Ra, #imm -> (rd, ra, imm);否则返回 None"""
if insn is None or mn(insn) != 'ADD':
return None
if insn.ops[2].type != O_IMM:
return None
return regname(insn, 0), regname(insn, 1), \
insn.ops[2].value & 0xFFFFFFFFFFFFFFFF

def _br_reg(insn):
"""BR Rd -> 寄存器名;否则 None"""
if insn is None or mn(insn) != 'BR':
return None
return regname(insn, 0)


# ---------------- 引用枚举(不依赖 idautils.CodeRefsTo) ----------------

def code_refs_to(ea):
"""枚举指向 ea 的代码引用(调用/跳转)"""
out, xb = [], ida_xref.xrefblk_t()
ok = xb.first_to(ea, ida_xref.XREF_ALL)
while ok:
if xb.type in (ida_xref.fl_JN, ida_xref.fl_JF,
ida_xref.fl_CN, ida_xref.fl_CF):
out.append(xb.frm)
ok = xb.next_to()
return out


# ---------------- 特征识别 ----------------

def is_lr_gadget(ea):
"""LR-gadget 特征: SUB SP,SP,#x ; STP X29,X30,[SP] ; LDR X0,[SP,#8] ;
LDP X29,X30,[SP] ; ADD SP,SP,#x ; RET"""
seq, cur = [], ea
for _ in range(6):
ins = dec(cur)
if ins is None or ins.size != 4:
return False
seq.append(mn(ins))
cur += ins.size
return seq == ['SUB', 'STP', 'LDR', 'LDP', 'ADD', 'RET']

def parse_flower(bl_ea, gadget_ea):
"""解析花指令,返回真实目标地址(LR+imm);不匹配返回 None"""
i0 = dec(bl_ea)
if not i0 or mn(i0) != 'BL' or i0.ops[0].addr != gadget_ea:
return None
# BL 之后取 5 条指令做窗口扫描(允许中间夹垃圾指令)
ins = []
cur = bl_ea + 4
for _ in range(5):
x = dec(cur)
if not x or x.size != 4:
return None
ins.append(x)
cur += 4
for k in range(3):
# 形式B: ADD Rd, X0, #imm ; BR Rd
a = _add_imm(ins[k])
if a and a[1] == 'X0' and _br_reg(ins[k + 1]) == a[0]:
return bl_ea + 4 + a[2]
# 形式A: MOV Ra, X0 ; ADD Ra, Ra, #imm ; BR Ra
if mn(ins[k]) == 'MOV' and regname(ins[k], 1) == 'X0':
ra = regname(ins[k], 0)
a2 = _add_imm(ins[k + 1])
if a2 and a2[0] == ra and a2[1] == ra \
and _br_reg(ins[k + 2]) == ra:
return bl_ea + 4 + a2[2]
return None

def funcs_touching(start, end):
"""收集与 [start, end) 相交的所有函数起点(带防死循环保护)"""
out = set()
ea = start
while ea < end:
f = ida_funcs.get_func(ea)
if f:
out.add(f.start_ea)
nxt = f.end_ea
ea = nxt if nxt > ea else ea + 4 # 防 end_ea<=ea 死循环
else:
ea += 4
ft = ida_funcs.get_func(end) # 目标处的函数也要被吸收
if ft:
out.add(ft.start_ea)
return out


# ---------------- 主流程 ----------------

def main():
print("[*] 扫描 LR-gadget 函数...")
gadgets = [ea for ea in idautils.Functions() if is_lr_gadget(ea)]
print("[*] gadget 共 %d 个" % len(gadgets))

# ---- 阶段1:纯扫描收集 (ref, target, 涉及函数起点),不做任何修改 ----
sites = [] # [(ref, target, {func_starts})]
skipped = {} # 跳过原因计数
for g in gadgets:
for ref in code_refs_to(g):
t = parse_flower(ref, g)
if t is None:
continue
seg = ida_segment.getseg(t)
if seg is None or not (seg.perm & ida_segment.SEGPERM_EXEC):
skipped['目标非代码'] = skipped.get('目标非代码', 0) + 1
print("[-] 0x%X 目标非代码(0x%X),跳过" % (ref, t))
continue
length = t - ref
# 物理下限 12 字节 = BL+ADD+BR;低于此值说明解析有误
if length < 12 or length > 0x1000 or length % 4:
skipped['跨度异常'] = skipped.get('跨度异常', 0) + 1
print("[-] 0x%X 跨度异常(len=0x%X),跳过" % (ref, length))
continue
sites.append((ref, t, funcs_touching(ref, t)))
print("[+] 0x%X ~ 0x%X (0x%X) -> B 0x%X"
% (ref, t - 4, length, t))

print("[*] 共匹配 %d 处,跳过 %s" % (len(sites), skipped or 0))
if DRY_RUN:
print("[*] DRY_RUN 模式,确认目标地址合理后改 DRY_RUN=False 重跑")
return
if not sites:
print("[*] 没有可处理的站点")
return

# ---- 阶段2:只 patch,不等待、不逐点分析(避免分析器风暴卡死) ----
print("[*] 开始 patch ...")
for ref, t, _ in sites:
length = t - ref
data = enc_b(ref, t) + NOP_BYTES * (length // 4 - 1)
ida_bytes.patch_bytes(ref, data)
print("[*] patch 完成,共 %d 处" % len(sites))

# ---- 阶段3:统一等待一次自动分析 ----
lo = min(ref for ref, _, _ in sites)
hi = max(t for _, t, _ in sites)
print("[*] 等待自动分析 [0x%X, 0x%X) ..." % (lo, hi))
if hasattr(ida_auto, "wait"):
ida_auto.wait() # IDA 7.x / 8.x
else:
ida_auto.plan_and_wait(lo, hi) # IDA 9.x
print("[*] 分析完成,重建函数 ...")

# ---- 阶段4:统一删函数 + 重建 ----
all_starts = set()
for _, _, starts in sites:
all_starts |= starts
failed = 0
for s in sorted(all_starts):
ida_funcs.del_func(s)
ida_funcs.add_func(s)
if not ida_funcs.get_func(s):
print("[!] 重建失败:0x%X(可手动在该地址按 P)" % s)
failed += 1

print("[*] 完成:patch %d 处,重建失败 %d 处" % (len(sites), failed))
print("[*] 保存:Edit -> Patch program -> Apply patches to input file")

main()

2.不透明谓词

1.TBNZ

这里的逻辑可以整理出是

// 先算一个永远成立的条件
W8 = W8 * W8 + 1; // MADD W8, W8, W8, W10
W8 = W8 % 7; // UDIV + MSUB
cond1 = (W8 != 0); // CSET NE

// 再掺一个无关条件混淆视听
cond2 = (W9 >= 0xDD); // 有符号比较,CSET GE

W8 = cond1 | cond2; // ORR
if (W8 & 1) goto loc_12C85C; // TBNZ W8, #0

x的平方加1再模7是不可能等于0 的,因为是偶数。所以这里肯定是要跳转的

所以nop法是删掉TBNZ和下面的LDR中间的一坨未识别的数据

2. B.NE

两数进行比较,这里两个数很明显不等于,所以肯定是要跳转的

3.AND-ADD-BR

.text:000000000012BC84             sub_12BC84                              ; DATA XREF: sub_12BC40+34↑o
.text:000000000012BC84 48 01 80 52 MOV W8, #0xA
.text:000000000012BC88 A8 83 1D F8 STUR X8, [X29,#-0x28]
.text:000000000012BC8C A8 83 5D F8 LDUR X8, [X29,#-0x28]
.text:000000000012BC90 09 05 00 11 ADD W9, W8, #1
.text:000000000012BC94 08 7D 09 1B MUL W8, W8, W9
.text:000000000012BC98 08 01 40 92 AND X8, X8, #1
.text:000000000012BC9C 09 00 00 90 ADRL X9, sub_12BCAC
.text:000000000012BC9C 29 B1 32 91
.text:000000000012BCA4 28 01 08 8B ADD X8, X9, X8
.text:000000000012BCA8 00 01 1F D6 BR X8
.text:000000000012BCA8 ; End of function sub_12BC84

这种也可以算单分支间接跳转,也是用来一个恒等表达式n*(n+1)必为偶数,所以&1的时候必是0,跳转地址是sub_12BCAC

4.UMULH-ADRL-ADD-BR

X8  = 0xD                    // 13
X9 = X8 + 1 // 14
X10 = X8 + 2 // 15
X8 = X8 * X9 * X10 // 13*14*15 = 2730

后面这个等价于%3,也是一个恒等式

(n * (n+1) * (n+1)) % 3 ==0

3.单分支间接跳转

1.MADD-BR

.text:000000000012BC14
.text:000000000012BC14 sub_12BC14 ; DATA XREF: sub_12BB14+60↑o
.text:000000000012BC14 ; sub_12BB8C+40↑o
.text:000000000012BC14 BF 03 1D F8 STUR XZR, [X29,#-0x30]
.text:000000000012BC18 C8 BA 80 52 MOV W8, #0x5D6
.text:000000000012BC1C A8 83 1C F8 STUR X8, [X29,#-0x38]
.text:000000000012BC20 A8 03 5D F8 LDUR X8, [X29,#-0x30]
.text:000000000012BC24 A9 83 5C F8 LDUR X9, [X29,#-0x38]
.text:000000000012BC28 0A 00 00 90 ADRL X10, sub_12BC40
.text:000000000012BC28 4A 01 31 91
.text:000000000012BC30 4B 01 08 8A AND X11, X10, X8
.text:000000000012BC34 48 01 08 CA EOR X8, X10, X8
.text:000000000012BC38 68 21 09 9B MADD X8, X11, X9, X8
.text:000000000012BC3C 00 01 1F D6 BR X8
.text:000000000012BC3C ; End of function sub_12BC14

这种只有一个分支,计算下来发现,x11=0,x8=x10,x8=0*0x5d6+x10然后跳转

所以跳转的地址就是sub_12BC40

2.MUL-BR

.text:000000000012BBEC
.text:000000000012BBEC E0 0B 40 F9 LDR X0, [SP,#arg_10]
.text:000000000012BBF0 ED 00 00 94 BL nullsub_46
.text:000000000012BBF4 E0 0B 00 F9 STR X0, [SP,#arg_10]
.text:000000000012BBF8 28 00 80 52 MOV W8, #1
.text:000000000012BBFC E8 23 00 F9 STR X8, [SP,#arg_40]
.text:000000000012BC00 E8 23 40 F9 LDR X8, [SP,#arg_40]
.text:000000000012BC04 09 00 00 90 ADRL X9, sub_12BCCC
.text:000000000012BC04 29 31 33 91
.text:000000000012BC0C 08 7D 09 9B MUL X8, X8, X9
.text:000000000012BC10 00 01 1F D6 BR X8

这个比较简单,简单的1*地址然后跳转,只不过这个赋值在栈上,然后再取回来这个操作阻止了反编译器的常量传播,所以反编译断了

总的收集+nop脚本

import re
import idc
import idaapi
import idautils
import ida_bytes
import ida_auto

SEG = ".text"
WIN = 20
APPLY = True # 先 False 看打印,核对 target 与 ADRL 符号一致再改 True

NOWRITE = {"STR", "STUR", "CMP", "CMN", "TST", "B", "BL", "BR", "BLR",
"RET", "NOP", "CBZ", "CBNZ", "TBZ", "TBNZ", "SVC", "BRK"}
CTRL = {"B", "BL", "BR", "BLR", "RET", "CBZ", "CBNZ", "TBZ", "TBNZ"}


# ---------- 基础工具 ----------
def mnem(ea):
return (idc.print_insn_mnem(ea) or "").upper()

def rn(t):
m = re.match(r"^[WX](\d+)$", (t or "").strip())
return int(m.group(1)) if m else None

def oreg(ea, i):
return rn(idc.print_operand(ea, i))

def otxt(ea, i):
return (idc.print_operand(ea, i) or "").strip()

def oimm(ea, i):
try:
return idc.get_operand_value(ea, i)
except Exception:
return None

def iscode(ea):
return ea != idaapi.BADADDR and idc.is_code(ida_bytes.get_full_flags(ea))

def xrefto(ea):
"""只取真正的地址引用,过滤 fl_F 顺序流(上次 B 到自己就是这里漏过滤)。"""
nxt = idc.next_head(ea, ea + 8)
for x in idautils.XrefsFrom(ea, 0):
if x.type == idaapi.fl_F or x.to == nxt:
continue
if iscode(x.to):
return x.to
return None

def window(br_ea, lo):
"""win[0]=BR,下标越大地址越小,最多 WIN 条。"""
win = [br_ea]
cur = br_ea
for _ in range(WIN):
p = idc.prev_head(cur, lo)
if p == idaapi.BADADDR or p < lo or not iscode(p):
break
win.append(p)
cur = p
return win

def find_def(win, r, start):
if r is None:
return None
for i in range(start, len(win)):
ea = win[i]
m = mnem(ea)
if m in CTRL:
return None
if m in NOWRITE:
continue
if oreg(ea, 0) == r:
return ea
return None

def adrl_parts(win, def_ea):
"""def_ea 是 ADRL 伪指令或 ADRP+ADD 后半时,返回 (target, [ea...])。"""
m = mnem(def_ea)
if m in ("ADRL", "ADRP"):
t = xrefto(def_ea)
return (t, [def_ea]) if t else (None, None)
if m == "ADD" and oreg(def_ea, 0) == oreg(def_ea, 1):
i = win.index(def_ea)
if i + 1 < len(win):
p = win[i + 1]
if mnem(p) == "ADRP" and oreg(p, 0) == oreg(def_ea, 0):
t = xrefto(def_ea) or xrefto(p)
if t:
return t, [def_ea, p]
return None, None

def trace_const(win, r, start):
"""追常量:MOV #imm,或一层 STR/LDR 同槽往返,支持 XZR。返回 (value, [eas])。"""
d = find_def(win, r, start)
if d is None:
return None
if mnem(d) == "MOV":
v = oimm(d, 1)
if v is not None:
return v, [d]
if mnem(d) in ("LDR", "LDUR"):
mem = otxt(d, 1)
di = win.index(d)
for j in range(di + 1, len(win)):
ea = win[j]
if mnem(ea) in ("STR", "STUR") and otxt(ea, 1) == mem:
if otxt(ea, 0) in ("XZR", "WZR"):
return 0, [d, ea]
sd = find_def(win, oreg(ea, 0), j + 1)
if sd and mnem(sd) == "MOV":
v = oimm(sd, 1)
if v is not None:
return v, [d, ea, sd]
return None


# ---------- 类型 2: MUL-BR(1 * addr) ----------
def match_mul_br(win, d):
mdi = win.index(d)
a, b = oreg(d, 1), oreg(d, 2)
for s in (a, b):
sd = find_def(win, s, mdi + 1)
if not sd:
continue
t, _ = adrl_parts(win, sd)
if t is None:
continue
other = b if s == a else a
tc = trace_const(win, other, mdi + 1)
if tc and tc[0] == 1:
return {"type": "MUL-BR", "target": t, "extra": "1*addr"}
return {"type": "MUL-BR?", "target": t, "extra": "乘数未证明为1"}
return None


# ---------- 类型 1: MADD-BR((a&0)*b + (a^0)) ----------
def match_madd_br(win, d):
if mnem(d) != "MADD":
return None
try:
if idc.get_operand_type(d, 3) != idaapi.o_reg or otxt(d, 3) in ("XZR", "WZR"):
return None
except Exception:
return None
mdi = win.index(d)
m1, m2, add = oreg(d, 1), oreg(d, 2), oreg(d, 3)

de = find_def(win, add, mdi + 1) # 加数必须是 EOR(addr, zero)
if not de or mnem(de) != "EOR":
return None
e1, e2 = oreg(de, 1), oreg(de, 2)
ide = win.index(de)

for mc in (m1, m2): # 某个乘数是 AND(addr, zero)
da = find_def(win, mc, mdi + 1)
if not da or mnem(da) != "AND":
continue
idi = win.index(da)
a1, a2 = oreg(da, 1), oreg(da, 2)
for ar, zr in ((a1, a2), (a2, a1)):
if ar is None or zr is None or zr not in (e1, e2):
continue
er = e1 if e2 == zr else e2
if er != ar:
continue
sd = find_def(win, ar, max(idi, ide) + 1)
if not sd:
continue
t, _ = adrl_parts(win, sd)
if t is None:
continue
zc = trace_const(win, zr, max(idi, ide) + 1)
if zc and zc[0] == 0:
return {"type": "MADD-BR", "target": t, "extra": "(a&0)*b+(a^0)"}
return None


# ---------- ADD-BR 族公用:拆 base(ADRL) + off ----------
def split_base_off(win, d):
mdi = win.index(d)
for i in (1, 2):
r = oreg(d, i)
if r is None:
continue
sd = find_def(win, r, mdi + 1)
if not sd:
continue
t, _ = adrl_parts(win, sd)
if t is not None:
return t, oreg(d, 2 if i == 1 else 1), mdi
return None


# ---------- 类型 3: AND-ADD-BR(n(n+1)&1 == 0) ----------
def match_and_add(win, d):
sp = split_base_off(win, d)
if not sp:
return None
t, off, mdi = sp
da = find_def(win, off, mdi + 1)
if not da or mnem(da) != "AND" or oimm(da, 2) != 1:
return None
dm = find_def(win, oreg(da, 1), win.index(da) + 1)
if not dm or mnem(dm) != "MUL":
return None
m1, m2 = oreg(dm, 1), oreg(dm, 2)
mi = win.index(dm)
n_r, d1 = None, None
for s in (m1, m2): # 找 ADD Xs+1, Xn, #1
o = m2 if s == m1 else m1
dd = find_def(win, s, mi + 1)
if dd and mnem(dd) == "ADD" and oreg(dd, 1) == o and oimm(dd, 2) == 1:
n_r, d1 = o, dd
break
if n_r is None:
return None
extra = "n(n+1)&1"
tc = trace_const(win, n_r, win.index(d1) + 1)
if tc:
n = tc[0]
extra = "n=%d, n(n+1)&1=%d" % (n, (n * (n + 1)) & 1)
return {"type": "AND-ADD-BR", "target": t, "extra": extra}


# ---------- 类型 4: UMULH-ADD-BR(n(n+1)(n+2)%3 == 0) ----------
def has_magic3(win):
for ea in win:
if "AAAAAAAAAAAAAAAB" in otxt(ea, 1).upper():
return True
if mnem(ea) in ("MOV", "MOVZ") and oimm(ea, 1) == 0xAAAB:
return True
return False

def match_umulh_add(win, d):
sp = split_base_off(win, d)
if not sp:
return None
t, off, mdi = sp
ds = find_def(win, off, mdi + 1) # off = SUBS(prod, q*3)
if not ds or mnem(ds) != "SUBS":
return None
prod, q3 = oreg(ds, 1), oreg(ds, 2)
si = win.index(ds)
dq = find_def(win, q3, si + 1) # q*3 = ADD q, q, q, LSL#1
if not dq or mnem(dq) != "ADD":
return None
op2 = otxt(dq, 2).replace(" ", "").upper()
if "LSL#1" not in op2 or rn(op2.split(",")[0]) != oreg(dq, 1):
return None
dl = find_def(win, oreg(dq, 1), win.index(dq) + 1) # q = LSR(u, #1)
if not dl or mnem(dl) != "LSR" or oimm(dl, 2) != 1:
return None
du = find_def(win, oreg(dl, 1), win.index(dl) + 1) # u = UMULH(prod, magic)
if not du or mnem(du) != "UMULH":
return None
if not has_magic3(win):
return None
extra = "n(n+1)(n+2)%3"
dp = find_def(win, prod, si + 1) # 可选:追 n 验证
if dp and mnem(dp) == "MUL":
for s in (oreg(dp, 1), oreg(dp, 2)):
d2 = find_def(win, s, win.index(dp) + 1)
if d2 and mnem(d2) == "ADD" and oimm(d2, 2) == 2:
tc = trace_const(win, oreg(d2, 1), win.index(d2) + 1)
if tc:
n = tc[0]
extra = "n=%d, n(n+1)(n+2)%%3=%d" % (n, (n * (n + 1) * (n + 2)) % 3)
return {"type": "UMULH-ADD-BR", "target": t, "extra": extra}


# ---------- 总分类 ----------
def classify(br_ea, lo):
win = window(br_ea, lo)
br_r = oreg(br_ea, 0)
if br_r is None:
return None
d = find_def(win, br_r, 1)
if d is None:
return None
m = mnem(d)
if m in ("MUL", "MADD"):
r = match_madd_br(win, d)
if r:
return r
try:
if idc.get_operand_type(d, 3) == idaapi.o_reg and otxt(d, 3) not in ("XZR", "WZR"):
return None # 真乘加,不是花
except Exception:
pass
return match_mul_br(win, d)
if m == "ADD":
return match_and_add(win, d) or match_umulh_add(win, d)
return None


# ---------- patch:只把 BR 改成 B target ----------
def patch_one(ea, info):
t = info["target"]
delta = t - ea
if delta & 3:
print("[SKIP ] BR=0x%X -> 0x%X 未 4 对齐" % (ea, t))
return False
off = delta >> 2
if not (-(1 << 25) <= off < (1 << 25)):
print("[SKIP ] BR=0x%X -> 0x%X 超出 ±128MB" % (ea, t))
return False
word = 0x14000000 | (off & 0x3FFFFFF)
bts = " ".join("%02X" % c for c in word.to_bytes(4, "little"))
print("[PATCH] %-13s BR=0x%X -> B 0x%X | off=%d word=0x%08X bytes=%s | %s%s"
% (info["type"], ea, t, off, word, bts, info["extra"],
"" if APPLY else " (dry-run)"))
if not APPLY:
return True
ida_bytes.patch_dword(ea, word)
idc.create_insn(ea)
try:
idaapi.add_cref(ea, t, idaapi.fl_JN)
except Exception:
pass
return True


def scan():
ida_auto.auto_wait()
res = {}
for seg in idautils.Segments():
if idc.get_segm_name(seg) != SEG:
continue
for ea in idautils.Heads(seg, idc.get_segm_end(seg)):
if mnem(ea) != "BR" or oreg(ea, 0) is None:
continue
fn = idaapi.get_func(ea)
info = classify(ea, fn.start_ea if fn else seg)
if info:
res[ea] = info

print("=" * 78)
from collections import Counter
cnt = Counter(v["type"] for v in res.values())
print("[SCAN] 共 %d 个:%s" % (len(res), dict(cnt)))
ok = 0
for ea in sorted(res):
if patch_one(ea, res[ea]):
ok += 1
if APPLY:
ida_auto.auto_wait()
idc.refresh_idaview_anyway()
print("[DONE] %d/%d APPLY=%s" % (ok, len(res), APPLY))
print("=" * 78)


# ---------- 修复以前被错误 patch 的 B(可选,正常流程用不到) ----------
def repair():
ida_auto.auto_wait()
fixed = 0
for seg in idautils.Segments():
if idc.get_segm_name(seg) != SEG:
continue
for ea in idautils.Heads(seg, idc.get_segm_end(seg)):
if mnem(ea) != "B":
continue
fn = idaapi.get_func(ea)
win = window(ea, fn.start_ea if fn else seg)
if len(win) < 2 or mnem(win[1]) not in ("MUL", "MADD", "ADD"):
continue
t = None
for w in win[1:]:
tt, _ = adrl_parts(win, w)
if tt:
t = tt
break
if t is None:
continue
cur = idc.get_operand_value(ea, 0)
if cur != t:
delta = t - ea
if delta & 3 or not (-(1 << 25) <= (delta >> 2) < (1 << 25)):
continue
word = 0x14000000 | ((delta >> 2) & 0x3FFFFFF)
print("[FIX] B@0x%X: 0x%X -> 0x%X word=0x%08X" % (ea, cur, t, word))
ida_bytes.patch_dword(ea, word)
idc.create_insn(ea)
fixed += 1
print("[REPAIR] 修复 %d 处" % fixed)


scan()
# repair() # 需要修复历史误 patch 时取消注释