- dist/: 一键补丁脚本 + 验收 README + 分析文档 - pkg/: patch_binary(方案C)/ patch_dev_mode(方案A 备选) - 完整逆向分析文档(双副本结构、base91、RSA 材料、偏移速查)
96 lines
3.7 KiB
Python
96 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
||
"""方案C(终稿):wxautox4 ui/main.pyd 常量 blob 精确补丁。
|
||
|
||
原理
|
||
----
|
||
授权守卫(ui/main.pyd 模块体)以
|
||
os.path.exists(os.path.join(<包路径>, ..., 'main.py'))
|
||
判定"源码运行/开发态"——为真即跳过全部授权校验(lolic/网络验证/exit 均不执行)。
|
||
|
||
本补丁把常量 blob 里的字符串 'main.py'(7 字节)原地改为 'base.py'(同为 7 字节,
|
||
wheel 安装后 <pkg>/ui/base.py 必然存在),使 exists() 恒真 → 守卫永远走开发态。
|
||
纯数据补丁:不改代码、不改指针、不影响重定位,且 base.py 与 main.pyd 同目录,
|
||
无论 join 的中间段是什么,拼接结果都指向存在的文件。
|
||
|
||
Nuitka 常量 blob 带完整性校验(RCDATA 资源头部 [CRC32:4][长度:4],加载器用
|
||
标准 zlib crc32 校验 payload),因此补丁后必须重算 CRC 写回头部——本脚本先对
|
||
原文件自校验定位 blob 头(不硬编码偏移),再打补丁 + 重算,双保险。
|
||
|
||
用法
|
||
----
|
||
python patch_binary.py <wxautox4安装目录> # 打补丁(自动备份 .bak)
|
||
python patch_binary.py <wxautox4安装目录> -r # 还原
|
||
"""
|
||
import shutil
|
||
import sys
|
||
import zlib
|
||
from pathlib import Path
|
||
|
||
PYD_REL = "ui/main.cp310-win_amd64.pyd"
|
||
OLD = b"umain.py\x00" # 'u' = Nuitka blob 的 unicode-str 标签
|
||
NEW = b"ubase.py\x00"
|
||
SEARCH_WINDOW = 0x8000 # blob 头在目标常量之前的 blob 起点处
|
||
|
||
|
||
def find_blob_headers(data: bytes, pos: int) -> list[tuple[int, int]]:
|
||
"""自校验定位所有覆盖 pos 的 blob 节:u32[h+4]=长度,u32[h]=zlib.crc32(data[h+8:h+8+len])。"""
|
||
hits = []
|
||
for h in range(max(0, pos - SEARCH_WINDOW), pos):
|
||
ln = int.from_bytes(data[h + 4 : h + 8], "little")
|
||
if not (0x100 < ln < 0x20000) or h + 8 + ln > len(data):
|
||
continue
|
||
if not (h + 8 <= pos < h + 8 + ln):
|
||
continue
|
||
crc = int.from_bytes(data[h : h + 4], "little")
|
||
if zlib.crc32(data[h + 8 : h + 8 + ln]) & 0xFFFFFFFF == crc:
|
||
hits.append((h, ln))
|
||
return hits
|
||
|
||
|
||
def main() -> None:
|
||
if len(sys.argv) < 2:
|
||
sys.exit(__doc__)
|
||
pkg = Path(sys.argv[1])
|
||
restore = "-r" in sys.argv
|
||
pyd = pkg / PYD_REL
|
||
bak = pyd.with_suffix(".pyd.bak")
|
||
|
||
if restore:
|
||
if not bak.exists():
|
||
sys.exit(f"!! 无备份可还原: {bak}")
|
||
shutil.copy2(bak, pyd)
|
||
print(f"[+] 已从 {bak} 还原")
|
||
return
|
||
|
||
data = pyd.read_bytes()
|
||
if NEW in data and OLD not in data:
|
||
print("[=] 已是补丁状态,跳过")
|
||
return
|
||
pos = data.find(OLD)
|
||
if pos == -1 or data.find(OLD, pos + 1) != -1:
|
||
sys.exit("!! 找不到 'main.py' 常量或出现多处——版本不符,放弃")
|
||
print(f"[*] 常量 'main.py' @ 文件偏移 {pos:#x}")
|
||
|
||
sections = find_blob_headers(data, pos)
|
||
if not sections:
|
||
sys.exit("!! 常量 blob 头自校验失败(CRC 不匹配)——放弃,未改动")
|
||
print(f"[*] blob 节 {len(sections)} 个覆盖目标常量:"
|
||
+ ", ".join(f"@{h:#x}(len {ln:#x})" for h, ln in sections))
|
||
|
||
buf = bytearray(data)
|
||
buf[pos : pos + len(OLD)] = NEW
|
||
for h, ln in sections:
|
||
new_crc = zlib.crc32(bytes(buf[h + 8 : h + 8 + ln])) & 0xFFFFFFFF
|
||
buf[h : h + 4] = new_crc.to_bytes(4, "little")
|
||
print(f"[*] blob 节 @{h:#x} CRC 重算为 {new_crc:#010x}")
|
||
if not bak.exists():
|
||
shutil.copy2(pyd, bak)
|
||
print(f"[+] 备份: {bak}")
|
||
pyd.write_bytes(bytes(buf))
|
||
print(f"[+] 已补丁: 'main.py'→'base.py' @ {pos:#x}")
|
||
print("[+] 守卫将恒判开发态,授权校验(含运行期复查)整体跳过")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|