• 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC) • 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库) • 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo 增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库
137 lines
5.3 KiB
Python
137 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Static verification of patch v2 (capstone).
|
|
|
|
Checks:
|
|
1. callback registration at 0x4efe28/2c now points to cave 0x41b8b0
|
|
2. cave disassembles without obvious corruption; entry guard present
|
|
3. key globals/strings land in the expected .data zero region
|
|
4. diff vs stock confined to the cave + registration bytes
|
|
"""
|
|
import sys, struct
|
|
from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS32, CS_MODE_LITTLE_ENDIAN
|
|
|
|
STOCK = "hiby_player_stock"
|
|
PATCHED = "hiby_player_patched_v2"
|
|
|
|
def rd(fn, off, n):
|
|
with open(fn, "rb") as f:
|
|
f.seek(off)
|
|
return f.read(n)
|
|
|
|
def disasm(data, vaddr):
|
|
md = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + CS_MODE_LITTLE_ENDIAN)
|
|
return list(md.disasm(data, vaddr))
|
|
|
|
ok = True
|
|
def check(cond, msg):
|
|
global ok
|
|
print(("PASS " if cond else "FAIL ") + msg)
|
|
if not cond:
|
|
ok = False
|
|
|
|
# 1. callback registration
|
|
pat = rd(PATCHED, 0x4EFE28 - 0x400000, 8)
|
|
lui = struct.unpack_from("<I", pat, 0)[0]
|
|
add = struct.unpack_from("<I", pat, 4)[0]
|
|
check(lui == 0x3C050041, f"0x4efe28 lui $a1, 0x41 (got {lui:#010x})")
|
|
check(add == 0x24A5B8B0, f"0x4efe2c addiu $a1, $a1, 0xb8b0 (got {add:#010x})")
|
|
|
|
# 2. cave disassembly
|
|
CAVE = 0x41B8B0
|
|
cave = rd(PATCHED, CAVE - 0x400000, 0x1000)
|
|
insns = disasm(cave, CAVE)
|
|
print(f"\ncave disasm ({len(insns)} instructions):")
|
|
for ins in insns[:14]:
|
|
print(f" {ins.address:#010x}: {ins.mnemonic} {ins.op_str}")
|
|
|
|
# entry guard structure (after gp setup): lw $v0,0($a2); slti $v1,$v0,7; bne...
|
|
first = insns[2]
|
|
check(first.mnemonic == "lw" and "v0" in first.op_str and "a2" in first.op_str,
|
|
f"cave[2] = lw $v0, 0($a2) (got '{first.mnemonic} {first.op_str}')")
|
|
check(insns[3].mnemonic == "slti", f"cave[3] = slti (type<7 guard) (got '{insns[3].mnemonic} {insns[3].op_str}')")
|
|
|
|
# scan for raw syscalls: they should appear in launch()
|
|
syscalls = [(i.address, i.mnemonic) for i in insns if i.mnemonic == "syscall"]
|
|
check(len(syscalls) >= 4, f"syscall instructions present ({len(syscalls)})")
|
|
|
|
# find addiu $v0,$zero,0xN patterns for fork/execve/waitpid (capstone prints hex)
|
|
def find_li_v0(val):
|
|
s = hex(val)
|
|
for i in range(len(insns) - 1):
|
|
a, b = insns[i], insns[i + 1]
|
|
if a.mnemonic == "addiu" and "$v0" in a.op_str and s in a.op_str:
|
|
return True
|
|
return False
|
|
check(find_li_v0(4002), "fork syscall number 4002 (0xfa2) present")
|
|
check(find_li_v0(4011), "execve syscall number 4011 (0xfab) present")
|
|
check(find_li_v0(4007), "waitpid syscall number 4007 (0xfa7) present")
|
|
|
|
# key control-flow references
|
|
def has_call(target):
|
|
for i in insns:
|
|
if i.mnemonic == "jal" and hex(target) in i.op_str:
|
|
return True
|
|
return False
|
|
check(has_call(0x49BE00), "calls 0x49be00 (create view)")
|
|
check(has_call(0x46C800), "calls 0x46c800 (activate view)")
|
|
check(has_call(0x46C2C0), "calls 0x46c2c0 (set callback)")
|
|
check(has_call(0x8E6C00), "calls opendir (0x8e6c00)")
|
|
check(has_call(0x8E6240), "calls access (0x8e6240)")
|
|
|
|
# v2.2: watchdog integration
|
|
import re as _re
|
|
def find_li_v0x(val):
|
|
s = hex(val)
|
|
for i in range(len(insns) - 1):
|
|
a, b = insns[i], insns[i + 1]
|
|
if a.mnemonic == "addiu" and "$v0" in a.op_str and s in a.op_str:
|
|
return True
|
|
return False
|
|
check(find_li_v0x(4037), "kill syscall 4037 present (watchdog cleanup)")
|
|
check(find_li_v0x(4002) and find_li_v0x(4011), "double fork (app+watchdog) syscalls present")
|
|
wds = rd(PATCHED, 0x41C8B0 - 0x400000, 400)
|
|
check(b"/usr/data/meow_watchdog\x00" in wds, "watchdog path string present")
|
|
# itoa: divu + mflo/mfhi present
|
|
divu = [i for i in insns if i.mnemonic == "divu"]
|
|
mflo = [i for i in insns if i.mnemonic == "mflo"]
|
|
check(len(divu) >= 1 and len(mflo) >= 1, f"itoa divu/mflo present ({len(divu)}/{len(mflo)})")
|
|
check(has_call(0x8E65B0), "calls readdir (0x8e65b0)")
|
|
|
|
# 3. string area
|
|
s = rd(PATCHED, 0x41C8B0 - 0x400000, 300) # STR_BASE = CAVE+0x1000 = 0x41c8b0
|
|
check(b"hiby_apps_main\x00" in s, "string 'hiby_apps_main' present")
|
|
check(b"/usr/data/mnt/sd_0/MeowAPPs\x00" in s, "string MeowAPPs dir present")
|
|
check(b"/proc/mounts\x00" in s, "string /proc/mounts present")
|
|
check("未检测到存储卡".encode() in s, "utf8 no-card hint present")
|
|
check("无可用应用".encode() in s, "utf8 no-app hint present")
|
|
check(b'"name":\x00' in s and b'"exec":\x00' in s, "json key strings present")
|
|
|
|
# 4. diff vs stock
|
|
st = open(STOCK, "rb").read()
|
|
pt = open(PATCHED, "rb").read()
|
|
assert len(st) == len(pt)
|
|
diffs = [i for i in range(len(st)) if st[i] != pt[i]]
|
|
regions = []
|
|
for d in diffs:
|
|
if regions and d - regions[-1][1] <= 4:
|
|
regions[-1][1] = d
|
|
else:
|
|
regions.append([d, d])
|
|
print(f"\ndiff bytes vs stock: {len(diffs)}, regions:")
|
|
for r in regions:
|
|
print(f" file {r[0]:#x}-{r[1]:#x} ({r[1]-r[0]+1} B)")
|
|
# must be exactly 2 regions: registration (0xE28/0xE2c) + cave (0x1B8B0..)
|
|
# cave code + strings extend from CAVE to CAVE+0x1000+~0x120 (strings)
|
|
CAVE_END = CAVE + 0x1000 + 0x160
|
|
ok_regions = all(
|
|
(0x4EFE28 - 0x400000 <= r[0] <= 0x4EFE30 - 0x400000) or
|
|
(0x47B820 - 0x400000 <= r[0] <= 0x47B824 - 0x400000) or
|
|
(0x436FC0 - 0x400000 <= r[0] <= 0x436FC4 - 0x400000) or
|
|
(CAVE - 0x400000 <= r[0] and r[1] <= CAVE_END - 0x400000)
|
|
for r in regions
|
|
)
|
|
check(ok_regions, "diff confined to registration + cave region")
|
|
|
|
print("\nRESULT:", "OK" if ok else "FAILED")
|
|
sys.exit(0 if ok else 1)
|