Files
rogee 7e64bb9c45 feat: wxautox4 v41.1.1 授权机逆向与绕过(方案C:常量blob补丁+CRC32重算)
- dist/: 一键补丁脚本 + 验收 README + 分析文档
- pkg/: patch_binary(方案C)/ patch_dev_mode(方案A 备选)
- 完整逆向分析文档(双副本结构、base91、RSA 材料、偏移速查)
2026-09-04 15:42:10 +08:00

104 lines
4.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()