Files
PROXY_GOD_MAC/proxy_chain_manager/mac_spoof.py
2026-05-23 22:09:43 -07:00

180 lines
6.1 KiB
Python

"""Randomize and restore NIC MAC addresses (Admin required)."""
from __future__ import annotations
import logging
import random
import re
import subprocess
import sys
from dataclasses import dataclass
from .firewall import is_admin
from .win_compat import probe as _win_probe
log = logging.getLogger(__name__)
_MAC_RE = re.compile(r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")
@dataclass
class NicMac:
name: str
mac: str
description: str = ""
def _run(args: list[str], timeout: float = 20.0) -> tuple[int, str, str]:
try:
r = subprocess.run(
args,
capture_output=True,
text=True,
timeout=timeout,
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
)
return r.returncode, r.stdout or "", r.stderr or ""
except Exception as e:
return 1, "", str(e)
def list_nics() -> list[NicMac]:
"""Physical/up adapters with current MAC.
Falls back to ``wmic nic`` for hosts without PowerShell 3.0 (Server 2008 R2).
"""
if sys.platform == "darwin":
code, out, _ = _run(["ifconfig"], timeout=10)
if code != 0:
return []
nics: list[NicMac] = []
current = ""
is_up = False
mac = ""
for line in out.splitlines():
if line and not line.startswith("\t") and ":" in line:
if current and is_up and mac:
nics.append(NicMac(name=current, mac=mac, description=current))
current = line.split(":", 1)[0].strip()
is_up = "UP" in line
mac = ""
elif "\tether " in line:
mac = line.strip().split()[1].replace("-", ":")
if current and is_up and mac:
nics.append(NicMac(name=current, mac=mac, description=current))
return nics
compat = _win_probe()
if not compat.has_net_cmdlets:
return _list_nics_wmic()
script = (
"Get-NetAdapter | Where-Object { $_.Status -ne 'Disabled' } | "
"Select-Object Name, MacAddress, InterfaceDescription | "
"ConvertTo-Json -Compress"
)
code, out, _ = _run(["powershell", "-NoProfile", "-Command", script])
if code != 0 or not out.strip():
return _list_nics_wmic()
import json
try:
data = json.loads(out)
except json.JSONDecodeError:
return []
rows = data if isinstance(data, list) else [data]
nics: list[NicMac] = []
for row in rows:
if not isinstance(row, dict):
continue
name = str(row.get("Name", "")).strip()
mac = str(row.get("MacAddress", "")).strip().replace("-", ":")
desc = str(row.get("InterfaceDescription", "")).strip()
if name and mac and mac != "00:00:00:00:00:00":
nics.append(NicMac(name=name, mac=mac, description=desc))
return nics
def random_mac() -> str:
"""Locally administered unicast MAC."""
b = [random.randint(0, 255) for _ in range(6)]
b[0] = (b[0] | 0x02) & 0xFE
return ":".join(f"{x:02X}" for x in b)
def _list_nics_wmic() -> list[NicMac]:
"""Server 2008 R2 / PowerShell 2.0 fallback via wmic."""
code, out, _ = _run([
"wmic", "nic", "where", "NetEnabled=true",
"get", "NetConnectionID,MACAddress,Name", "/format:list",
])
if code != 0 or not out:
return []
nics: list[NicMac] = []
cur = {"id": "", "mac": "", "name": ""}
for ln in out.splitlines():
s = ln.strip()
if not s:
if cur["id"] and cur["mac"]:
mac = cur["mac"].replace("-", ":")
if mac and mac != "00:00:00:00:00:00":
nics.append(NicMac(name=cur["id"], mac=mac, description=cur["name"]))
cur = {"id": "", "mac": "", "name": ""}
continue
if s.startswith("NetConnectionID="):
cur["id"] = s.split("=", 1)[1].strip()
elif s.startswith("MACAddress="):
cur["mac"] = s.split("=", 1)[1].strip()
elif s.startswith("Name="):
cur["name"] = s.split("=", 1)[1].strip()
if cur["id"] and cur["mac"]:
mac = cur["mac"].replace("-", ":")
if mac and mac != "00:00:00:00:00:00":
nics.append(NicMac(name=cur["id"], mac=mac, description=cur["name"]))
return nics
def set_mac(adapter: str, mac: str) -> tuple[bool, str]:
if not is_admin():
return False, "Administrator required to change MAC."
if not _win_probe().has_net_cmdlets:
return False, (
"MAC change requires PowerShell 3.0+ (Windows 8 / Server 2012+). "
"Detected legacy PowerShell — feature unavailable on this host."
)
mac = mac.replace("-", ":").upper()
if not _MAC_RE.match(mac.replace(":", "-")):
return False, f"Invalid MAC: {mac}"
ps = (
f'$a = Get-NetAdapter -Name "{adapter}" -ErrorAction Stop; '
f'Set-NetAdapter -Name $a.Name -MacAddress "{mac}" -Confirm:$false'
)
code, _, err = _run(["powershell", "-NoProfile", "-Command", ps])
if code != 0:
return False, err or "Set-NetAdapter failed"
log.info("MAC set %s%s", adapter, mac)
return True, f"{adapter}{mac}"
def spoof_all_physical(snapshot: dict[str, str] | None = None) -> tuple[dict[str, str], list[str]]:
"""Randomize MAC on non-virtual adapters. Returns (original_map, log lines)."""
originals: dict[str, str] = dict(snapshot or {})
logs: list[str] = []
skip_hints = ("virtual", "vmware", "hyper-v", "loopback", "bluetooth", "wan miniport")
for nic in list_nics():
low = (nic.description + nic.name).lower()
if any(h in low for h in skip_hints):
continue
if nic.name not in originals:
originals[nic.name] = nic.mac
new_mac = random_mac()
ok, msg = set_mac(nic.name, new_mac)
logs.append(msg if ok else f"{nic.name}: {msg}")
return originals, logs
def restore_macs(originals: dict[str, str]) -> list[str]:
logs: list[str] = []
for name, mac in originals.items():
ok, msg = set_mac(name, mac)
logs.append(msg if ok else f"{name}: {msg}")
return logs