first commit
This commit is contained in:
292
proxy_chain_manager/telemetry_kill.py
Normal file
292
proxy_chain_manager/telemetry_kill.py
Normal file
@@ -0,0 +1,292 @@
|
||||
"""Reversibly disable Windows telemetry / data collection.
|
||||
|
||||
Targets, in order of leak severity:
|
||||
|
||||
• DiagTrack service — primary telemetry pipeline ("Connected User
|
||||
Experiences and Telemetry"). Phones home every
|
||||
few minutes.
|
||||
• dmwappushservice — WAP Push routing (DSMS), historically tied to
|
||||
DiagTrack. Disabled when present.
|
||||
• AllowTelemetry (HKLM) — DataCollection policy. 0 = lowest tier.
|
||||
• Activity History — EnableActivityFeed / Upload / PublishUserActivities.
|
||||
• Advertising ID — HKCU\\…\\AdvertisingInfo\\Enabled.
|
||||
• Cortana Bing search — HKCU\\…\\Search\\BingSearchEnabled.
|
||||
• Scheduled tasks — Compatibility Appraiser + CEIP Consolidator
|
||||
+ UsbCeip.
|
||||
|
||||
Every change is snapshotted before mutation; ``restore_telemetry`` rolls back.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
import winreg
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from .firewall import is_admin
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_SERVICES = ("DiagTrack", "dmwappushservice")
|
||||
|
||||
_SCHED_TASKS = (
|
||||
r"\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser",
|
||||
r"\Microsoft\Windows\Customer Experience Improvement Program\Consolidator",
|
||||
r"\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip",
|
||||
)
|
||||
|
||||
# (hive, path, value_name, dword_when_killed)
|
||||
_REG_KILLS = (
|
||||
(winreg.HKEY_LOCAL_MACHINE,
|
||||
r"SOFTWARE\Policies\Microsoft\Windows\DataCollection",
|
||||
"AllowTelemetry", 0),
|
||||
(winreg.HKEY_LOCAL_MACHINE,
|
||||
r"SOFTWARE\Policies\Microsoft\Windows\System",
|
||||
"EnableActivityFeed", 0),
|
||||
(winreg.HKEY_LOCAL_MACHINE,
|
||||
r"SOFTWARE\Policies\Microsoft\Windows\System",
|
||||
"PublishUserActivities", 0),
|
||||
(winreg.HKEY_LOCAL_MACHINE,
|
||||
r"SOFTWARE\Policies\Microsoft\Windows\System",
|
||||
"UploadUserActivities", 0),
|
||||
(winreg.HKEY_LOCAL_MACHINE,
|
||||
r"SOFTWARE\Policies\Microsoft\Windows\CloudContent",
|
||||
"DisableWindowsConsumerFeatures", 1),
|
||||
(winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo",
|
||||
"Enabled", 0),
|
||||
(winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Search",
|
||||
"BingSearchEnabled", 0),
|
||||
(winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Search",
|
||||
"CortanaConsent", 0),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TelemetrySnapshot:
|
||||
service_state: dict[str, str] = field(default_factory=dict) # name -> 'auto'|'manual'|'disabled'|'absent'
|
||||
sched_tasks_state: dict[str, bool] = field(default_factory=dict) # task -> was_enabled
|
||||
reg_values: list[dict] = field(default_factory=list)
|
||||
|
||||
|
||||
def _run(args: list[str], timeout: float = 12.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)
|
||||
|
||||
|
||||
# ── service helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
def _service_start_type(name: str) -> str:
|
||||
code, out, _ = _run(["sc", "qc", name])
|
||||
if code != 0:
|
||||
return "absent"
|
||||
for ln in out.splitlines():
|
||||
s = ln.strip().lower()
|
||||
if "start_type" in s:
|
||||
if "2" in s or "auto" in s:
|
||||
return "auto"
|
||||
if "3" in s or "demand" in s or "manual" in s:
|
||||
return "manual"
|
||||
if "4" in s or "disabled" in s:
|
||||
return "disabled"
|
||||
return "absent"
|
||||
|
||||
|
||||
def _set_service_disabled(name: str) -> bool:
|
||||
_run(["sc", "stop", name], timeout=15)
|
||||
code, _, _ = _run(["sc", "config", name, "start=", "disabled"])
|
||||
return code == 0
|
||||
|
||||
|
||||
def _set_service_start(name: str, want: str) -> bool:
|
||||
start_value = {"auto": "auto", "manual": "demand", "disabled": "disabled"}.get(want)
|
||||
if not start_value:
|
||||
return False
|
||||
_run(["sc", "stop", name], timeout=10)
|
||||
code, _, _ = _run(["sc", "config", name, "start=", start_value])
|
||||
if want == "auto":
|
||||
_run(["sc", "start", name], timeout=15)
|
||||
return code == 0
|
||||
|
||||
|
||||
# ── scheduled-task helpers ──────────────────────────────────────────────────
|
||||
|
||||
def _task_is_enabled(name: str) -> bool | None:
|
||||
code, out, _ = _run(["schtasks", "/Query", "/TN", name])
|
||||
if code != 0:
|
||||
return None
|
||||
for ln in out.splitlines():
|
||||
s = ln.strip().lower()
|
||||
if s.startswith("status:") or "ready" in s or "disabled" in s:
|
||||
if "disabled" in s:
|
||||
return False
|
||||
if "ready" in s or "running" in s:
|
||||
return True
|
||||
return True # assume ready when query succeeded
|
||||
|
||||
|
||||
def _set_task_state(name: str, enable: bool) -> bool:
|
||||
code, _, _ = _run([
|
||||
"schtasks", "/Change", "/TN", name,
|
||||
"/Enable" if enable else "/Disable",
|
||||
])
|
||||
return code == 0
|
||||
|
||||
|
||||
# ── registry helpers ────────────────────────────────────────────────────────
|
||||
|
||||
def _read_reg_dword(hive: int, path: str, name: str) -> tuple[bool, int | None]:
|
||||
"""Returns (present, value)."""
|
||||
try:
|
||||
with winreg.OpenKey(hive, path, 0, winreg.KEY_QUERY_VALUE) as key:
|
||||
v, _ = winreg.QueryValueEx(key, name)
|
||||
return True, int(v)
|
||||
except OSError:
|
||||
return False, None
|
||||
|
||||
|
||||
def _write_reg_dword(hive: int, path: str, name: str, value: int) -> bool:
|
||||
try:
|
||||
with winreg.CreateKeyEx(hive, path, 0, winreg.KEY_SET_VALUE) as key:
|
||||
winreg.SetValueEx(key, name, 0, winreg.REG_DWORD, value)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def _delete_reg_value(hive: int, path: str, name: str) -> None:
|
||||
try:
|
||||
with winreg.OpenKey(hive, path, 0, winreg.KEY_SET_VALUE) as key:
|
||||
winreg.DeleteValue(key, name)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
# ── public API ──────────────────────────────────────────────────────────────
|
||||
|
||||
def telemetry_status() -> dict[str, str]:
|
||||
"""Read-only snapshot for the audit panel."""
|
||||
out: dict[str, str] = {}
|
||||
for svc in _SERVICES:
|
||||
out[f"svc.{svc}"] = _service_start_type(svc)
|
||||
pres, val = _read_reg_dword(
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
r"SOFTWARE\Policies\Microsoft\Windows\DataCollection",
|
||||
"AllowTelemetry",
|
||||
)
|
||||
out["AllowTelemetry"] = "0 (killed)" if pres and val == 0 else (
|
||||
f"{val}" if pres else "unset"
|
||||
)
|
||||
pres, val = _read_reg_dword(
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo",
|
||||
"Enabled",
|
||||
)
|
||||
out["AdvertisingID"] = "off" if pres and val == 0 else (
|
||||
"on" if pres and val == 1 else "unset"
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def engage_telemetry_kill() -> tuple[TelemetrySnapshot | None, list[str]]:
|
||||
logs: list[str] = []
|
||||
if not is_admin():
|
||||
return None, ["Telemetry kill skipped — needs Administrator."]
|
||||
snap = TelemetrySnapshot()
|
||||
|
||||
for svc in _SERVICES:
|
||||
st = _service_start_type(svc)
|
||||
snap.service_state[svc] = st
|
||||
if st == "absent":
|
||||
continue
|
||||
if _set_service_disabled(svc):
|
||||
logs.append(f"Service '{svc}' stopped + disabled (was {st}).")
|
||||
else:
|
||||
logs.append(f"Service '{svc}' could not be disabled.")
|
||||
|
||||
for task in _SCHED_TASKS:
|
||||
was = _task_is_enabled(task)
|
||||
if was is None:
|
||||
continue
|
||||
snap.sched_tasks_state[task] = was
|
||||
if was:
|
||||
if _set_task_state(task, enable=False):
|
||||
logs.append(f"Scheduled task disabled: {task}")
|
||||
|
||||
for hive, path, name, kill_val in _REG_KILLS:
|
||||
present, prev = _read_reg_dword(hive, path, name)
|
||||
snap.reg_values.append({
|
||||
"hive": "HKLM" if hive == winreg.HKEY_LOCAL_MACHINE else "HKCU",
|
||||
"path": path,
|
||||
"name": name,
|
||||
"present": present,
|
||||
"prev": prev,
|
||||
})
|
||||
if _write_reg_dword(hive, path, name, kill_val):
|
||||
logs.append(f"reg: {('HKLM' if hive == winreg.HKEY_LOCAL_MACHINE else 'HKCU')}\\{path}\\{name} → {kill_val}")
|
||||
else:
|
||||
logs.append(f"reg write failed: {path}\\{name}")
|
||||
|
||||
return snap, logs
|
||||
|
||||
|
||||
def restore_telemetry(snap: TelemetrySnapshot | None) -> list[str]:
|
||||
if snap is None or not is_admin():
|
||||
return []
|
||||
logs: list[str] = []
|
||||
|
||||
for svc, prev in snap.service_state.items():
|
||||
if prev == "absent":
|
||||
continue
|
||||
if _set_service_start(svc, prev):
|
||||
logs.append(f"Service '{svc}' restored → {prev}.")
|
||||
|
||||
for task, was in snap.sched_tasks_state.items():
|
||||
if was:
|
||||
_set_task_state(task, enable=True)
|
||||
logs.append(f"Scheduled task re-enabled: {task}")
|
||||
|
||||
for entry in snap.reg_values:
|
||||
hive = winreg.HKEY_LOCAL_MACHINE if entry["hive"] == "HKLM" else winreg.HKEY_CURRENT_USER
|
||||
path = entry["path"]
|
||||
name = entry["name"]
|
||||
if entry["present"]:
|
||||
_write_reg_dword(hive, path, name, int(entry["prev"]))
|
||||
logs.append(f"reg restore: {entry['hive']}\\{path}\\{name} ← {entry['prev']}")
|
||||
else:
|
||||
_delete_reg_value(hive, path, name)
|
||||
logs.append(f"reg removed (was absent): {entry['hive']}\\{path}\\{name}")
|
||||
return logs
|
||||
|
||||
|
||||
def snapshot_to_json(snap: TelemetrySnapshot | None) -> str:
|
||||
if snap is None:
|
||||
return ""
|
||||
return json.dumps({
|
||||
"services": snap.service_state,
|
||||
"tasks": snap.sched_tasks_state,
|
||||
"reg": snap.reg_values,
|
||||
})
|
||||
|
||||
|
||||
def snapshot_from_json(raw: str) -> TelemetrySnapshot | None:
|
||||
if not raw:
|
||||
return None
|
||||
try:
|
||||
d = json.loads(raw)
|
||||
return TelemetrySnapshot(
|
||||
service_state=dict(d.get("services") or {}),
|
||||
sched_tasks_state={k: bool(v) for k, v in (d.get("tasks") or {}).items()},
|
||||
reg_values=list(d.get("reg") or []),
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
Reference in New Issue
Block a user