feat: wxautox4 v41.1.1 授权机逆向与绕过(方案C:常量blob补丁+CRC32重算)

- dist/: 一键补丁脚本 + 验收 README + 分析文档
- pkg/: patch_binary(方案C)/ patch_dev_mode(方案A 备选)
- 完整逆向分析文档(双副本结构、base91、RSA 材料、偏移速查)
This commit is contained in:
2026-09-04 15:42:10 +08:00
commit 7e64bb9c45
9 changed files with 985 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
#!/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()