- dist/: 一键补丁脚本 + 验收 README + 分析文档 - pkg/: patch_binary(方案C)/ patch_dev_mode(方案A 备选) - 完整逆向分析文档(双副本结构、base91、RSA 材料、偏移速查)
104 lines
4.0 KiB
Python
104 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
||
"""wxautox4 v41.1.1 授权绕过一键补丁(方案C:常量 blob 精确补丁 + CRC32 重算)
|
||
|
||
用法(与安装 wxautox4 的同一个 Python):
|
||
pip install wxautox4==41.1.1
|
||
python patch_wxautox4.py # 打补丁(自动备份 .bak,幂等)
|
||
python patch_wxautox4.py -r # 还原
|
||
|
||
原理:授权守卫以 os.path.exists(<pkg>/ui/…/ 'main.py') 判定"源码开发态",
|
||
为真即跳过全部授权校验。本补丁把 ui/main.*.pyd 常量 blob 里的 'main.py'
|
||
(7 字节)等长改为 'base.py'(同目录必然存在),使守卫恒走开发态。
|
||
Nuitka 常量 blob 带完整性校验([CRC32:4][长度:4] 头 + zlib crc32),
|
||
故补丁后重算 CRC 写回。纯数据补丁,不改任何代码。
|
||
"""
|
||
import importlib.util
|
||
import sys
|
||
import zlib
|
||
from pathlib import Path
|
||
|
||
OLD = b"umain.py\x00" # 'u' = Nuitka blob 的 unicode-str 标签
|
||
NEW = b"ubase.py\x00"
|
||
SEARCH_WINDOW = 0x8000
|
||
|
||
|
||
def locate_package() -> Path:
|
||
# 注意:绝不能 import wxautox4 —— 那会执行守卫直接 sys.exit。
|
||
spec = importlib.util.find_spec("wxautox4")
|
||
if spec and spec.submodule_search_locations:
|
||
return Path(list(spec.submodule_search_locations)[0])
|
||
for p in sys.path: # pip 装在别的解释器时兜底扫 sys.path
|
||
cand = Path(p) / "wxautox4"
|
||
if (cand / "ui").is_dir():
|
||
return cand
|
||
sys.exit("!! 未找到已安装的 wxautox4(先用目标 Python 执行 pip install wxautox4==41.1.1,再用同一 Python 运行本脚本)")
|
||
|
||
|
||
def find_sections(data: bytes, pos: int) -> list[tuple[int, int]]:
|
||
"""暴力自校验定位所有覆盖 pos 的 blob 节(不硬编码偏移)。"""
|
||
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 patch_pyd(pyd: Path, restore: bool) -> None:
|
||
bak = pyd.with_suffix(pyd.suffix + ".bak")
|
||
if restore:
|
||
if not bak.exists():
|
||
print(f"[!] {pyd.name} 无备份,跳过还原")
|
||
return
|
||
pyd.write_bytes(bak.read_bytes())
|
||
print(f"[+] 已还原 {pyd}")
|
||
return
|
||
|
||
data = pyd.read_bytes()
|
||
if NEW in data and OLD not in data:
|
||
print(f"[=] {pyd.name} 已是补丁状态")
|
||
return
|
||
pos = data.find(OLD)
|
||
if pos == -1 or data.find(OLD, pos + 1) != -1:
|
||
sys.exit(f"!! {pyd.name} 中 'main.py' 常量缺失或多处——版本不符,放弃")
|
||
|
||
sections = find_sections(data, pos)
|
||
if not sections:
|
||
sys.exit(f"!! {pyd.name} 常量 blob CRC 自校验失败——放弃,未改动")
|
||
|
||
buf = bytearray(data)
|
||
buf[pos : pos + len(OLD)] = NEW
|
||
for h, ln in sections:
|
||
crc = zlib.crc32(bytes(buf[h + 8 : h + 8 + ln])) & 0xFFFFFFFF
|
||
buf[h : h + 4] = crc.to_bytes(4, "little")
|
||
|
||
if not bak.exists():
|
||
bak.write_bytes(data)
|
||
print(f"[+] 备份: {bak.name}")
|
||
pyd.write_bytes(bytes(buf))
|
||
print(f"[+] 已补丁 {pyd.name}: 'main.py'→'base.py' @ {pos:#x},"
|
||
f"blob CRC 重算×{len(sections)} 节——守卫恒走开发态,授权校验整体跳过")
|
||
|
||
|
||
def main() -> None:
|
||
restore = "-r" in sys.argv
|
||
if not restore:
|
||
print("== wxautox4 v41.1.1 一键补丁(方案C:blob 字符串 + CRC32 重算)==")
|
||
pkg = locate_package()
|
||
pyds = sorted((pkg / "ui").glob("main.cp*-win_amd64.pyd"))
|
||
if not pyds:
|
||
sys.exit(f"!! {pkg / 'ui'} 下未找到 main.cp*-win_amd64.pyd")
|
||
for pyd in pyds:
|
||
patch_pyd(pyd, restore)
|
||
if not restore:
|
||
print("== 完成。验证:python -c \"from wxautox4 import WeChat; print('guard passed')\" ==")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|