Created
July 30, 2025 08:41
-
-
Save jimmy947788/495ebbd6a6660ab219a7ccc525272f37 to your computer and use it in GitHub Desktop.
IDA 腳本讀取 blutter產生的 pp.json 並將常數字串,註解在對應的IDA反編譯指令碼旁
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import idaapi | |
import idautils | |
import idc | |
import ida_search | |
import ida_kernwin | |
import os | |
import ida_bytes | |
import ida_segment | |
import json | |
from collections import defaultdict | |
# -------------------------------------------------- | |
# 日誌功能(可改用 loguru,如需請自行加回) | |
def info(msg): | |
print(f"[INFO] {msg}") | |
def error(msg): | |
print(f"[ERROR] {msg}") | |
def debug(msg): | |
pass # print(f"[DEBUG] {msg}") | |
# -------------------------------------------------- | |
# 載入 pp.json,支援 offset 標準化 | |
def load_pp_json(pp_path): | |
if not os.path.exists(pp_path): | |
error("pp.json file not exists.") | |
raise Exception("pp.json file not exists.") | |
with open(pp_path, 'r') as f: | |
raw = json.load(f) | |
pp_data = dict() | |
# 標準化所有 key 格式:去除大寫、補零、多種 [pp+xxx] 型式 | |
for k, v in raw.items(): | |
if k.startswith("[pp+") and k.endswith("]"): | |
off_hex = k[4:-1].lower().lstrip("0x").zfill(1) | |
pp_data[f"[pp+0x{off_hex}]"] = v | |
pp_data[f"[pp+{int(off_hex,16)}]"] = v | |
pp_data[f"[pp+{off_hex}]"] = v | |
return pp_data | |
def get_pp_value(pp_data, offset): | |
# 標準化 key,嘗試多種形式 | |
keys = [ | |
f"[pp+0x{offset:x}]", | |
f"[pp+{offset}]", | |
f"[pp+{hex(offset)}]", | |
f"[pp+{offset:x}]" | |
] | |
for k in keys: | |
if k in pp_data: | |
return pp_data[k] | |
return None | |
# -------------------------------------------------- | |
# 匹配 pattern helper | |
def match_add_pool(insn): | |
# 支援 ADD/ADR/ADRP MOV | |
# 必須是 ADD X?, X27, #imm,LSL#12 | |
return (insn.itype in [idaapi.ARM_add, idaapi.ARM_adrp, idaapi.ARM_adr, idaapi.ARM_mov]) and \ | |
(insn.ops[1].type == idaapi.o_reg and insn.ops[1].reg in [156,155]) and \ | |
(insn.ops[2].type in [idaapi.o_imm, idaapi.o_displ]) | |
def match_ldr_pool(insn): | |
# LDR/STR Wn/Xn, [Xn, #offset] | |
return (insn.itype == idaapi.ARM_ldr) and \ | |
(insn.ops[1].type == idaapi.o_displ) | |
# -------------------------------------------------- | |
# 搜尋核心 | |
def search_and_annotate(pp_data): | |
pool_xrefs = defaultdict(list) | |
unmapped_offsets = set() | |
total_segments = sum(1 for _ in idautils.Segments()) | |
processed_segments = 0 | |
for seg_start in idautils.Segments(): | |
seg = idaapi.getseg(seg_start) | |
seg_name = ida_segment.get_segm_name(seg) | |
seg_type = seg.type | |
if seg_type != idaapi.SEG_CODE: | |
continue | |
info(f"搜尋段: {seg_name}") | |
start_ea = seg_start | |
end_ea = seg.end_ea | |
ea = start_ea | |
seg_size = end_ea - start_ea | |
last_progress = -1 | |
while ea < end_ea: | |
# 進度顯示 | |
current_progress = int(100 * (ea - start_ea) / seg_size) | |
if current_progress % 10 == 0 and current_progress != last_progress: | |
info(f" 進度 {current_progress}% @ 0x{ea:x}") | |
last_progress = current_progress | |
# 嘗試解出 ADD 指令 | |
insn = idaapi.insn_t() | |
if not idaapi.decode_insn(insn, ea): | |
ea += 4 | |
continue | |
if not match_add_pool(insn): | |
ea += 4 | |
continue | |
add_reg = insn.ops[0].reg | |
base_reg = insn.ops[1].reg | |
imm_val = insn.ops[2].value | |
shift = getattr(insn.ops[2], "shft", 12) | |
offset = imm_val << shift if shift else imm_val | |
# 在 1~3 條指令內找對應 LDR | |
for lookahead in range(1, 4): | |
ldr_ea = ea + lookahead * 4 | |
ldr_insn = idaapi.insn_t() | |
if not idaapi.decode_insn(ldr_insn, ldr_ea): | |
continue | |
if match_ldr_pool(ldr_insn) and ldr_insn.ops[0].reg == add_reg: | |
ldr_offset = ldr_insn.ops[1].addr | |
total_offset = offset + ldr_offset | |
# 清除舊註解 | |
idc.set_cmt(ldr_ea, "", 0) | |
# 查 pp | |
value = get_pp_value(pp_data, total_offset) | |
if value is None: | |
unmapped_offsets.add(total_offset) | |
comment = f"[pp+0x{total_offset:x}] (未找到pp常數)" | |
else: | |
comment = f"[pp+0x{total_offset:x}] {value}" | |
idc.set_cmt(ldr_ea, comment, 0) | |
pool_xrefs[total_offset].append(ldr_ea) | |
info(f" 註解 {hex(ldr_ea)} ← {comment}") | |
break # 若找到即跳出 | |
ea += 4 | |
processed_segments += 1 | |
info(f" 完成 {seg_name} ({processed_segments}/{total_segments})") | |
return pool_xrefs, unmapped_offsets | |
# -------------------------------------------------- | |
# 執行 | |
def main(): | |
pp_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "pp.json") | |
info(f"pp_path: {pp_path}") | |
info("載入 pp.json ...") | |
pp_data = load_pp_json(pp_path) | |
info("載入 pp.json 成功!") | |
pool_xrefs, unmapped_offsets = search_and_annotate(pp_data) | |
# 最後報表 | |
print("\n===== 所有 pool offset XREF 報表 =====") | |
for offset, addrs in sorted(pool_xrefs.items()): | |
print(f"[pp+0x{offset:x}] used at:", ", ".join([hex(a) for a in addrs])) | |
if unmapped_offsets: | |
print("\n===== 未對應到 pool 字串 offset(請補齊 pp.json)=====") | |
for off in sorted(unmapped_offsets): | |
print(f" [pp+0x{off:x}]") | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment