Files
MeowOS Dev 245bee2946 MeowOS for HiBy R3 Pro II — 公共发布版本(历史已重置)
• 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC)
• 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库)
• 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo
增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库
2026-08-30 01:12:34 +08:00

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)