Files
xieyi-weixin-/wecom_native_builds.py
2026-09-29 15:51:28 +08:00

146 lines
5.1 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.
"""Exact-build layouts and version classification.
The version shown by Windows is useful for choosing a compatibility family, but
it is not strong enough to select native addresses. A patched client can keep
the same product version family while moving every relevant RVA. Native
sending requires an exact executable digest or a complete structural ABI match.
Automatically relocated builds still require current-account receipt evidence.
"""
from dataclasses import dataclass, field
import hashlib
from pathlib import Path
import re
_VERSION_RE = re.compile(r"(?<![\w.])v?(\d{1,5}(?:\.\d{1,5}){3})(?![\w.])", re.IGNORECASE)
@dataclass(frozen=True)
class NativeBuild:
version: str
sha256: str
service_root: int
message_vtable: int
login_vtable: int
factory: int
send: int
release: int
send_stub: str
send_argument_bytes: int
verified: bool
adapter_revision: str = "original-text-v1"
automatic: bool = False
live_checks: tuple = field(default=(), repr=False)
@property
def adapter_id(self):
return f"wxwork-{self.version}-x86"
# Addresses use the PE preferred base 0x400000; runtime rebasing happens in NativeClient.
BUILDS = (
NativeBuild("5.0.10.6023", "47b5dfafc8def0b41a648610f1ba08bbf15f710c6f81c08d36c485bd82ce2842",
0xf7fb57c, 0xb936800, 0xb92c530, 0x818e560, 0x966c1f0, 0x656340,
"send", 12, True),
NativeBuild("5.0.11.6018", "ca7280999c0ae011bf2fac296c24f796595f3558df784edc50dd3f9a6edac5d9",
0xfe3c444, 0xbe21a84, 0xbe1730c, 0x8545200, 0x9a5f400, 0x66c5a0,
"send_with_options", 16, False),
# 5.0.11.6026 x86: independently located against the signed client image
# installed on the target machine. It remains disabled until this build's
# first real server receipt is recorded by the explicit validation action.
NativeBuild("5.0.11.6026", "b597539664b69e78c7edaa7e4edce46dccc864fafb7caa00764b60ee48eccea6",
0xfe7bfbc, 0xbe5766c, 0xbe4cc1c, 0x856eec0, 0x9a8b780, 0x66d050,
"send_with_options", 16, False, "original-text-v1-6026"),
)
# A family is intentionally broader than an adapter. It lets the UI explain
# that 5.0.11.6026 was detected as a known line without treating
# 5.0.11.6018's code layout as compatible with it.
KNOWN_VERSION_FAMILIES = {
"5.0.10": "5.0.10 x86 客户端系列",
"5.0.11": "5.0.11 x86 客户端系列",
}
def normalize_version(value):
"""Return a canonical version from Windows/process metadata."""
if value is None:
return ""
text = str(value).strip()
if not text:
return ""
matches = list(_VERSION_RE.finditer(text))
if len(matches) != 1:
return ""
components = matches[0].group(1).split(".")
if any(int(component) > 65535 for component in components):
return ""
return ".".join(str(int(component)) for component in components)
def version_family(value):
"""Return the three-component family key for a client version."""
normalized = normalize_version(value)
parts = normalized.split(".")
return ".".join(parts[:3]) if len(parts) >= 3 else normalized
def classify_version(value):
"""Describe a detected version without granting native-send capability."""
normalized = normalize_version(value)
family = version_family(normalized)
build = build_for_version(normalized) if normalized else None
if build is not None:
state = "supported_exact"
elif family in KNOWN_VERSION_FAMILIES:
state = "known_family_new_patch"
elif normalized:
state = "unknown"
else:
state = "missing"
return {
"raw": "" if value is None else str(value),
"normalized": normalized,
"family": family,
"familyLabel": KNOWN_VERSION_FAMILIES.get(family, ""),
"state": state,
"adapterId": build.adapter_id if build is not None else "",
}
def build_for_hash(digest):
value = str(digest or "").strip().lower()
known = next((build for build in BUILDS if build.sha256.lower() == value), None)
if known is not None:
return known
from wecom_native_adaptation import cached_build
return cached_build(value)
def sha256_file(path):
"""Hash one client image for read-only build identification.
The caller must still validate the live process and account before a native
write. This helper only makes an exact build recognizable when the Windows
version resource is missing, localized, or formatted differently.
"""
digest = hashlib.sha256()
with Path(path).open("rb") as handle:
while chunk := handle.read(1024 * 1024):
digest.update(chunk)
return digest.hexdigest()
def build_for_version(version):
normalized = normalize_version(version)
return next((build for build in BUILDS if build.version == normalized), None)
def verified_adapter(adapter_id):
return any(build.adapter_id == adapter_id and build.verified for build in BUILDS)
def client_support_text():
return ";".join(f"{build.version} x86({'已实测' if build.verified else '首次发送验证后启用'})" for build in BUILDS)