72 lines
2.6 KiB
Python
72 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Profile a cryptominer's configuration from static strings.
|
|
|
|
Extracts stratum pool endpoints, wallet addresses, algorithm identifiers, miner family strings,
|
|
and resource-control/stealth settings. Reads bytes statically, defangs pool endpoints, and
|
|
executes nothing.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ASCII_RE = re.compile(rb"[\x20-\x7e]{4,}")
|
|
RE_STRATUM = re.compile(r"stratum\+(?:tcp|ssl)://[^\s\"'<>]+", re.I)
|
|
RE_MONERO = re.compile(r"\b4[0-9AB][0-9A-Za-z]{93}\b")
|
|
RE_BTC = re.compile(r"\b(?:bc1[0-9a-z]{20,60}|[13][a-km-zA-HJ-NP-Z1-9]{25,34})\b")
|
|
RE_ETH = re.compile(r"\b0x[a-fA-F0-9]{40}\b")
|
|
ALGOS = ["rx/0", "rx/wow", "cn/r", "cn/2", "cn-heavy", "argon2", "kawpow", "ethash", "randomx",
|
|
"cryptonight"]
|
|
MINER_IDS = ["xmrig", "xmr-stak", "minerd", "cpuminer", "nbminer", "phoenixminer", "t-rex",
|
|
"lolminer", "nanominer"]
|
|
RESOURCE_KEYS = ["max-cpu-usage", "cpu-priority", "donate-level", "background", "max-threads-hint",
|
|
"huge-pages", "idle", "watchdog"]
|
|
|
|
|
|
def _strings(data: bytes) -> str:
|
|
return "\n".join(m.decode("latin-1", "ignore") for m in ASCII_RE.findall(data))
|
|
|
|
|
|
def _defang(s: str) -> str:
|
|
return s.replace("stratum", "stratum-x").replace("://", "[://]").replace(".", "[.]")
|
|
|
|
|
|
def profile(path: str) -> dict:
|
|
blob = _strings(Path(path).read_bytes())
|
|
low = blob.lower()
|
|
pools = sorted({_defang(p) for p in RE_STRATUM.findall(blob)})
|
|
wallets = {
|
|
"monero": sorted(set(RE_MONERO.findall(blob)))[:10],
|
|
"bitcoin": sorted(set(RE_BTC.findall(blob)))[:10],
|
|
"ethereum": sorted(set(RE_ETH.findall(blob)))[:10],
|
|
}
|
|
wallets = {k: v for k, v in wallets.items() if v}
|
|
algos = sorted({a for a in ALGOS if a in low})
|
|
miners = sorted({m for m in MINER_IDS if m in low})
|
|
resource = sorted({r for r in RESOURCE_KEYS if r in low})
|
|
return {
|
|
"pool_endpoints": pools,
|
|
"wallet_addresses": wallets,
|
|
"algorithms": algos,
|
|
"miner_family": miners,
|
|
"resource_controls": resource,
|
|
"is_likely_cryptominer": bool(pools or (miners and algos)),
|
|
}
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="Profile a cryptominer")
|
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
p = sub.add_parser("profile")
|
|
p.add_argument("file")
|
|
args = parser.parse_args(argv)
|
|
print(json.dumps(profile(args.file), indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|