#Malware related RMMSystem that uses Metastealer C2 traffic, tracker traffic: /pmxs/logs
"""
Full DGA (Domain Generation Algorithm) replication of sub_14000BF00 and friends.
Calling generate_domains() replicates the exact output of the binary with the
hardcoded seed, count, width, and word list found in sub_14000BF00.
"""
from __future__ import annotations
# ---------------------------------------------------------------------------
# Constants extracted directly from sub_14000BF00
# ---------------------------------------------------------------------------
WORDLIST_RAW = (
"void,star,orbit,probe,moon,sun,core,crust,dust,gas,rock,ice,ring,disc,belt,"
"flare,ray,beam,light,dark,cold,heat,mass,weight,force,pull,push,drift,glide,"
"spin,tilt,axis,pole,cap,crater,ridge,peak,rift,vent,plume,cloud,mist,fog,veil,"
"spark,flash,glow,pulse,wave,tide,flow,stream,path,track,zone,rim,edge,brink,"
"shell,skin,hull,hatch,port,bay,dock,deck,mast,sail,wing,fin,nose,tail,tank,"
"fuel,bolt,nut,wire,cord,link,joint,gear,lens,glass,mirror,film,chip,grid,plug,"
"switch,knob,dial,screen,frame,base,stand,mount,arm,dish,beam,mast,boom"
)
SEED = 0x1AED # 6893
LEVELS = 1000 # number of domains to generate
WIDTH = 3 # word picks per domain
TLD = "xyz" # suffix pointed to by the hardcoded address 8026488
# ---------------------------------------------------------------------------
# sub_140007B50 — parse comma-separated word list into a list of strings
# ---------------------------------------------------------------------------
def parse_wordlist(raw: str) -> list[str]:
"""
Replicates sub_140007B50.
Parses a comma-separated string into a list of tokens, mirroring the
binary's exact behaviour:
- Iterates character by character, accumulating into a buffer.
- On comma: trims leading (space, tab) and trailing (space, tab, CR, LF)
whitespace from the accumulated token, then pushes if non-empty.
- After the loop: flushes the final token (no trailing comma in the list).
- Duplicates are preserved (e.g. 'beam' and 'mast' appear twice in the
hardcoded word list).
"""
LEAD_TRIM = {' ', '\t'} # chars stripped from left
TRAIL_TRIM = {' ', '\t', '\r', '\n'} # chars stripped from right
results: list[str] = []
accumulator: list[str] = []
def flush() -> None:
token = "".join(accumulator)
# trim leading whitespace (mirrors forward scan)
start = 0
while start < len(token) and token[start] in LEAD_TRIM:
start += 1
# trim trailing whitespace (mirrors backward scan)
end = len(token)
while end > start and token[end - 1] in TRAIL_TRIM:
end -= 1
trimmed = token[start:end]
if trimmed:
results.append(trimmed)
accumulator.clear()
for ch in raw:
if ch == ',':
flush()
else:
accumulator.append(ch)
flush() # mirrors sub_140005AD0 call — flush final token after loop
return results
# ---------------------------------------------------------------------------
# u32 — keep arithmetic in unsigned 32-bit range (x86-64 register semantics)
# ---------------------------------------------------------------------------
def u32(value: int) -> int:
return value & 0xFFFFFFFF
# ---------------------------------------------------------------------------
# sub_140006F10 — hash-driven DGA core
# ---------------------------------------------------------------------------
def _generate_paths(
seed: int,
levels: int,
width: int,
nodes: list[str],
suffix: str,
) -> list[str]:
"""
Replicates sub_140006F10.
Pseudorandomly selects 'width' words per row using a rolling 32-bit hash,
inserts '-' when hash is even (and col > 1), appends '.<suffix>'.
"""
if not nodes or levels <= 0 or width <= 0:
return []
node_count = len(nodes)
# 'step' mirrors the binary's scan of offset+16 on each 32-byte node struct.
# offset+16 in MSVC std::string is the SIZE field, so the scan finds the
# maximum string length. step = max_len + 1.
step = max(len(w) for w in nodes) + 1
results: list[str] = []
h = u32(seed)
for level in range(1, levels + 1):
h = u32(level + step * h)
parts: list[str] = []
for col in range(1, width + 1):
# a2 += (a2 / v19) ^ (a2 % ((unsigned int)v13 * v16 * v19))
# Operator precedence: division and modulo bind before XOR.
# u32 wraps only the final addition result.
mod = u32(node_count * level * col)
h = u32(h + ((h // col) ^ (h % mod)))
if col != 1 and (h & 1) == 0:
parts.append("-")
parts.append(nodes[h % node_count])
parts.append(".")
parts.append(suffix)
results.append("".join(parts))
return results
# ---------------------------------------------------------------------------
# sub_14000BF00 — top-level entry point
# ---------------------------------------------------------------------------
def generate_domains(
seed: int = SEED,
levels: int = LEVELS,
width: int = WIDTH,
tld: str = TLD,
wordlist: str = WORDLIST_RAW,
) -> list[str]:
"""
Replicates sub_14000BF00 — the full DGA bootstrap.
Parses the hardcoded word list, runs the hash-driven generator, and
returns the list of generated domain names.
Args:
seed: Initial hash value (default: 0x1AED as hardcoded in binary).
levels: Number of domains to generate (default: 1000).
width: Word selections per domain (default: 3).
tld: Domain suffix / TLD (default: 'com').
wordlist: Comma-separated word pool (default: hardcoded binary string).
Returns:
List of generated domain name strings.
"""
nodes = parse_wordlist(wordlist)
return _generate_paths(seed, levels, width, nodes, tld)
# ---------------------------------------------------------------------------
# Demo
# ---------------------------------------------------------------------------
if __name__ == "__main__":
domains = generate_domains()
print(f"Generated {len(domains)} domains (seed=0x{SEED:04X}, width={WIDTH}, tld={TLD!r})")
print(f"Word pool: {len(parse_wordlist(WORDLIST_RAW))} words")
print(f"Step (max_word_len+1): {max(len(w) for w in parse_wordlist(WORDLIST_RAW)) + 1}\n")
print("First 10 domains:")
for i, d in enumerate(domains[:10], 1):
print(f" {i:>4}. {d}")
print(f"\nLast 5 domains:")
for i, d in enumerate(domains[-5:], len(domains) - 4):
print(f" {i:>4}. {d}")
# Known-correct spot checks from the binary
assert domains[0] == "lensclouddisc.xyz", f"Domain 1 mismatch: {domains[0]}"
assert domains[1] == "drift-joint-axis.xyz", f"Domain 2 mismatch: {domains[1]}"
assert domains[2] == "flow-pull-edge.xyz", f"Domain 3 mismatch: {domains[2]}"
print("\nSpot-check assertions passed.")
assert generate_domains() == domains
print("Determinism check passed.")