Cpu killer: Skirtumas tarp puslapio versijų
Jump to navigation
Jump to search
(Naujas puslapis: Scriptas skirtas automatiškai killinti userių procesus kurie pastoviai ėda >90% cpu time. Naudoju Free Shells projektui jau eilę metų, čia jau antra scripto iteracija, pirm...) |
(Jokio skirtumo)
|
Dabartinė 22:45, 2 lapkričio 2025 versija
Scriptas skirtas automatiškai killinti userių procesus kurie pastoviai ėda >90% cpu time. Naudoju Free Shells projektui jau eilę metų, čia jau antra scripto iteracija, pirmoji buvo parašyta ant bash.. Scriptas taip pat "decloakina" hidden procesus, t.y pakeistais pavadinimais, pervadina procesą su prefix'u "_too_much_cpu_time". Pervadintas nepasileis automatiškai, jeigu uždėtas ant cron ar kokio kito auto paleisties mechanizmo t.y systemd-user.
#!/usr/bin/env python3
"""
CPU Killer Daemon
- Continuously monitors processes
- Kills if >90% CPU for 10+ minutes
- Renames real binary
- Runs as background service
"""
import os
import sys
import psutil
import shutil
import time
from datetime import datetime
from pathlib import Path
from collections import defaultdict
# ============================= CONFIG =============================
CPU_THRESHOLD = 90.0 # % CPU usage
TIME_THRESHOLD = 600 # seconds (10 minutes)
CHECK_INTERVAL = 30 # check every 30 sec
EXCEPTION_USERS = {'root', 'polkitd', '_chrony', 'postgres', 'nginx', 'devnull', 'zabbix', 'www-data', 'systemd-network', 'systemd-timesync', 'systemd-resolve'} # <--- ADD YOUR USERS HERE
LOG_FILE = "/root/tools/cpu_killer.log"
DRY_RUN = False # Set to True for testing (no kill/rename)
PID_FILE = "/root/tools/cpu_killer.pid"
# ==================================================================
def log(msg):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"[{ts}] {msg}"
print(line)
try:
with open(LOG_FILE, "a") as f:
f.write(line + "\n")
except:
pass
def daemonize():
if os.fork(): sys.exit(0)
os.setsid()
if os.fork(): sys.exit(0)
sys.stdout.flush()
sys.stderr.flush()
with open('/dev/null', 'r') as dev_null:
os.dup2(dev_null.fileno(), sys.stdin.fileno())
with open(LOG_FILE, "a") as f:
os.dup2(f.fileno(), sys.stdout.fileno())
os.dup2(f.fileno(), sys.stderr.fileno())
# Write PID
with open(PID_FILE, "w") as f:
f.write(str(os.getpid()))
class ProcessTracker:
def __init__(self):
self.tracked = {} # pid -> {start_time, cpu_samples}
def update(self, proc):
pid = proc.pid
if pid not in self.tracked:
self.tracked[pid] = {
'start_time': time.time(),
'cpu_samples': []
}
self.tracked[pid]['cpu_samples'].append(proc.cpu_percent())
# Keep last 20 samples (~10 min at 30s interval)
if len(self.tracked[pid]['cpu_samples']) > 20:
self.tracked[pid]['cpu_samples'].pop(0)
def is_offender(self, pid):
data = self.tracked.get(pid)
if not data or len(data['cpu_samples']) < 10:
return False
# Average CPU over last ~5-10 min
avg_cpu = sum(data['cpu_samples']) / len(data['cpu_samples'])
runtime = time.time() - data['start_time']
return avg_cpu >= CPU_THRESHOLD and runtime >= TIME_THRESHOLD
def cleanup(self, pid):
self.tracked.pop(pid, None)
tracker = ProcessTracker()
def get_real_binary(proc):
try:
exe = f"/proc/{proc.pid}/exe"
if not os.path.exists(exe):
return None
path = os.readlink(exe)
if "(deleted)" in path:
path = path.replace(" (deleted)", "").strip()
return path if os.path.exists(path) else None
except:
return None
def rename_binary(bin_path):
if not bin_path or not os.path.exists(bin_path):
return
path = Path(bin_path)
new_name = path.parent / f"{path.stem}_too_much_cpu_time{path.suffix}"
i = 1
while new_name.exists():
new_name = path.parent / f"{path.stem}_too_much_cpu_time.{i}{path.suffix}"
i += 1
log(f" -> Renaming: {path.name} → {new_name.name}")
if not DRY_RUN:
try:
shutil.move(str(path), str(new_name))
except Exception as e:
log(f" -> Rename failed: {e}")
def kill_process(proc):
try:
cmd = " ".join(proc.cmdline()[:3]) + ("..." if len(proc.cmdline()) > 3 else "")
log(f"KILLING PID {proc.pid} ({cmd}) | User: {proc.username()} | CPU: ~{proc.cpu_percent():.1f}%")
if not DRY_RUN:
proc.terminate()
try:
gone, alive = psutil.wait_procs([proc], timeout=5)
if alive:
log(f" -> Force kill")
for p in alive:
p.kill()
except:
pass
return True
except Exception as e:
log(f" -> Kill failed: {e}")
return False
def main_loop():
log("CPU Killer started (daemon mode)")
seen_pids = set()
while True:
try:
current_pids = {p.pid for p in psutil.process_iter()}
# Cleanup dead
for pid in list(tracker.tracked.keys()):
if pid not in current_pids:
tracker.cleanup(pid)
for proc in psutil.process_iter(['pid', 'name', 'username', 'cpu_percent', 'create_time', 'cmdline']):
try:
username = proc.info['username']
if not username or username in EXCEPTION_USERS or username.startswith('_'):
continue
if proc.pid < 100:
continue
# Measure CPU (short interval)
cpu = proc.cpu_percent(interval=0.1)
if cpu <= 0:
continue
tracker.update(proc)
if tracker.is_offender(proc.pid):
bin_path = get_real_binary(proc)
if bin_path:
log(f"OFFENDER DETECTED: PID {proc.pid} | User: {username} | Binary: {bin_path}")
else:
log(f"OFFENDER DETECTED: PID {proc.pid} | User: {username} | Binary: [IN-MEMORY]")
if kill_process(proc):
if bin_path:
rename_binary(bin_path)
tracker.cleanup(proc.pid)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
time.sleep(CHECK_INTERVAL)
except KeyboardInterrupt:
log("CPU Killer stopped by user")
break
except Exception as e:
log(f"Unexpected error: {e}")
time.sleep(CHECK_INTERVAL)
if os.path.exists(PID_FILE):
os.remove(PID_FILE)
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "stop":
if os.path.exists(PID_FILE):
with open(PID_FILE) as f:
pid = int(f.read().strip())
try:
os.kill(pid, 15)
log(f"Stopped CPU Killer (PID {pid})")
except:
log("Failed to stop (not running?)")
os.remove(PID_FILE)
else:
log("Not running")
sys.exit(0)
if os.path.exists(PID_FILE):
log("Already running!")
sys.exit(1)
daemonize()
main_loop()
Systemd unit'as:
[Unit]
Description=CPU Killer Daemon
After=network.target
[Service]
Type=simple
ExecStart=/root/tools/cpu_killer.py
ExecStop=/root/tools/cpu_killer.py stop
Restart=always
RestartSec=10
StandardOutput=null
StandardError=journal
User=root
PIDFile=/root/tools/cpu_killer.pid
[Install]
WantedBy=multi-user.target