#!/usr/bin/env python3 """Offline reproducer for xxbb PLServerPool domain generation. Native path (type-0x01 `_generateDomainsLocked` → helper at 0x81fe0): srandom(murmur2_0x12345678(seed)) for i in 0 .. count-1: burn murmur2(seed + "%ld" % i) % 10000 calls to random() 15 chars from [a-z0-9] via random() % 36 format as "%@.icu" The shared Deployment/Reporting pools both pass the reporting-c CFString as `seed`. This script performs no DNS lookups. """ from __future__ import annotations import argparse MASK32 = 0xFFFFFFFF MURMUR_M = 0x5BD1E995 MURMUR_SEED = 0x12345678 ALNUM = "abcdefghijklmnopqrstuvwxyz0123456789" LABEL_LEN = 15 TLD = ".icu" NATIVE_POOL_SIZE = 512 KNOWN_SEEDS = { "channel-c": "202700cfb1ad3de68e11239dcc26c30b", "deployment": "321fb0c812b46265421b5ad9654c2b81", "reporting": "68143bfa7130bb97a642196db0292a12", } def murmur_hash2(value: str, seed: int = MURMUR_SEED) -> int: data = value.encode("utf-8") result = (seed ^ len(data)) & MASK32 offset = 0 while offset + 4 <= len(data): block = int.from_bytes(data[offset : offset + 4], "little") block = (block * MURMUR_M) & MASK32 block ^= block >> 24 block = (block * MURMUR_M) & MASK32 result = (result * MURMUR_M) & MASK32 result ^= block offset += 4 tail = data[offset:] if len(tail) == 3: result ^= tail[2] << 16 if len(tail) >= 2: result ^= tail[1] << 8 if len(tail) >= 1: result ^= tail[0] result = (result * MURMUR_M) & MASK32 result ^= result >> 13 result = (result * MURMUR_M) & MASK32 result ^= result >> 15 return result & MASK32 def _to_i32(value: int) -> int: value &= MASK32 return value - 0x100000000 if value >= 0x80000000 else value def _good_rand(value: int) -> int: """Park–Miller LCG used by Apple/BSD srandom() (not the weak 1103515245 seed).""" x = _to_i32(value) if x == 0: x = 123459876 hi = int(x / 127773) # C truncates toward zero; Python // floors lo = x - hi * 127773 x = 16807 * lo - 2836 * hi if x < 0: x += 0x7FFFFFFF return x & MASK32 class BSDRandom: """Apple libsystem_c TYPE_3 random()/srandom() (deg=31, sep=3).""" DEG = 31 SEP = 3 def __init__(self) -> None: self.state = [0] * self.DEG self.f = self.SEP self.r = 0 def srandom(self, seed: int) -> None: self.state[0] = seed & MASK32 for i in range(1, self.DEG): self.state[i] = _good_rand(self.state[i - 1]) self.f = self.SEP self.r = 0 for _ in range(10 * self.DEG): self.random() def random(self) -> int: self.state[self.f] = (self.state[self.f] + self.state[self.r]) & MASK32 value = (self.state[self.f] >> 1) & 0x7FFFFFFF self.f += 1 if self.f >= self.DEG: self.f = 0 self.r += 1 else: self.r += 1 if self.r >= self.DEG: self.r = 0 return value def generate_domains(seed: str, count: int = 5) -> list[str]: """First `count` hosts the native pool would try (pool itself holds 512).""" if not 1 <= count <= NATIVE_POOL_SIZE: raise ValueError(f"count must be between 1 and {NATIVE_POOL_SIZE}") rng = BSDRandom() rng.srandom(murmur_hash2(seed)) domains: list[str] = [] for index in range(count): burn = murmur_hash2(f"{seed}{index}") % 10000 for _ in range(burn): rng.random() label = "".join(ALNUM[rng.random() % len(ALNUM)] for _ in range(LABEL_LEN)) domains.append(f"{label}{TLD}") return domains def main() -> None: parser = argparse.ArgumentParser( description="Reproduce xxbb PLServerPool DGA candidates offline (no network access)." ) parser.add_argument( "seed", nargs="?", default="channel-c", help="channel-c, deployment, reporting, or a literal seed", ) parser.add_argument("-n", "--count", type=int, default=5) args = parser.parse_args() seed = KNOWN_SEEDS.get(args.seed, args.seed) print(f"seed={seed}") for index, domain in enumerate(generate_domains(seed, args.count), 1): print(f"{index:03d} {domain}") if __name__ == "__main__": main()