
终端是攻击链的最后一环,也是数据泄露的起点。传统杀软依赖特征匹配,面对无文件攻击、内存马、白利用时形同虚设。现代终端安全的核心是持续采集遥测、建立行为基线、检测异常偏离、并快速响应闭环。本文从进程监控讲到持久化检测,给出可落地的代码。
一切检测始于高质量的进程事件。Windows 用 ETW/Sysmon,Linux 用 eBPF。Linux 侧用 bpftrace 快速验证:
# 监控 execve,输出父进程、子进程与参数
bpftrace -e '
tracepoint:syscalls:sys_enter_execve {
printf("[%d->%d] %s %s\n", pid, tid, comm, str(args->filename));
}'生产环境用 Python 封装 auditd 日志解析:
import re, json
from dataclasses import dataclass, asdict
@dataclass
class ProcEvent:
ts: float
pid: int
ppid: int
exe: str
cmdline: str
uid: int
AUDIT_RE = re.compile(
r"type=EXECVE.*?ppid=(\d+).*?pid=(\d+).*?uid=(\d+).*?exe=\"([^\"]+)\"")
def parse_audit(line: str) -> ProcEvent | None:
m = AUDIT_RE.search(line)
if not m:
return None
ppid, pid, uid, exe = m.groups()
args = re.findall(r'a\d+="([^"]*)"', line)
return ProcEvent(ts=0.0, pid=int(pid), ppid=int(ppid),
exe=exe, cmdline=" ".join(args), uid=int(uid))关键点:采集父进程ID,否则无法重建攻击链。
规则只能覆盖已知攻击。基线用统计方法捕捉异常。
from collections import defaultdict, Counter
import math
class Baseline:
def __init__(self):
self.pc = defaultdict(Counter) # parent -> child 频次
def train(self, events: list[ProcEvent]):
for e in events:
parent = e.exe.rsplit("/", 1)[-1]
child = e.cmdline.split()[0].rsplit("/", 1)[-1] if e.cmdline else "?"
self.pc[parent][child] += 1
def score(self, parent: str, child: str) -> float:
"""负对数似然,越大越异常"""
c = self.pc.get(parent)
if not c:
return 8.0
prob = (c.get(child, 0) + 1) / (sum(c.values()) + len(c))
return -math.log(prob)
# 使用
b = Baseline()
b.train(events)
print(b.score("nginx", "bash")) # 极高:Web 服务不应派生 shell
print(b.score("bash", "ls")) # 极低:正常交互典型高危组合:nginx→bash(WebShell)、winword→powershell(钓鱼)、java→sh(反序列化利用)。
持久化是攻击者立足的关键,覆盖多个位置。
import os, subprocess, hashlib
PERSIST_PATHS = {
"cron": ["/etc/crontab", "/var/spool/cron"],
"systemd": ["/etc/systemd/system", "/lib/systemd/system"],
"ssh": ["/root/.ssh/authorized_keys", "/home"],
"profile": ["/etc/profile.d", "/etc/rc.local"],
}
def scan_persistence() -> list[dict]:
hits = []
for category, paths in PERSIST_PATHS.items():
for p in paths:
if not os.path.exists(p):
continue
for root, _, files in os.walk(p) if os.path.isdir(p) else [(p, [], [p])]:
for f in files:
fp = os.path.join(root, f)
try:
content = open(fp, encoding="utf-8", errors="ignore").read()
except (OSError, PermissionError):
continue
# 检测可疑命令
for kw in ("curl", "wget", "base64 -d", "/dev/tcp/", "nc -e"):
if kw in content:
hits.append({"path": fp, "keyword": kw,
"category": category})
return hits更专业的做法是对持久化位置做完整性基线:首次记录哈希,后续变化即告警。
def snapshot(paths: list[str]) -> dict[str, str]:
snap = {}
for p in paths:
if os.path.isfile(p):
snap[p] = hashlib.sha256(open(p, "rb").read()).hexdigest()
return snap
def diff(before: dict, after: dict) -> list[str]:
changed = []
for k, v in after.items():
if k not in before:
changed.append(f"新增: {k}")
elif before[k] != v:
changed.append(f"修改: {k}")
for k in before:
if k not in after:
changed.append(f"删除: {k}")
return changed无文件攻击常驻内存,不落盘,需检测异常内存区域。
# 检测 Linux 进程的可疑内存映射(rwx 段)
def scan_rwx_maps(pid: int) -> list[str]:
suspicious = []
try:
with open(f"/proc/{pid}/maps") as f:
for line in f:
if "rwx" in line: # 可读写可执行,高危
suspicious.append(line.strip())
except (FileNotFoundError, PermissionError):
pass
return suspicious
# 遍历所有进程
def scan_all():
for pid in os.listdir("/proc"):
if pid.isdigit():
hits = scan_rwx_maps(int(pid))
if hits:
print(f"[PID {pid}] 可疑 RWX 段: {hits}")Windows 侧对应检测 VirtualAllocEx + WriteProcessMemory + CreateRemoteThread 组合,这是进程注入的经典三部曲。
C2 通信是攻击者的生命线,监控异常外连至关重要。
import psutil
def suspicious_conns() -> list[dict]:
out = []
for c in psutil.net_connections(kind="inet"):
if c.status != "ESTABLISHED" or not c.raddr:
continue
ip, port = c.raddr.ip, c.raddr.port
# 检测非常规端口 + 无进程归属
if port in (4444, 5555, 8888, 1337) or (port > 10000 and c.pid is None):
out.append({"local": f"{c.laddr.ip}:{c.laddr.port}",
"remote": f"{ip}:{port}", "pid": c.pid})
return out实际生产中,应结合威胁情报库做 IP 信誉匹配,而非仅靠端口猜测。
检测只是开始,响应才是价值。自动化响应需谨慎,高危动作必须人工确认。
def respond(alert: dict, auto: bool = False):
severity = alert["severity"]
pid = alert.get("pid")
if severity == "critical" and auto:
# 隔离进程:先挂起再取证
try:
p = psutil.Process(pid)
p.suspend()
# 采集内存、打开文件、网络连接后再 kill
print(f"[隔离] PID {pid} 已挂起")
except psutil.NoSuchProcess:
pass
else:
print(f"[告警] 需人工确认: {alert}")响应优先级:取证 > 隔离 > 清除。直接 kill 会丢失内存证据,应先在挂起状态下 dump。
终端安全的专业性,体现在采集的精准、基线的可靠、检测的分层、响应的闭环。规则覆盖已知,基线捕捉未知,内存与网络检测补盲,响应沉淀证据。把这套体系落地,终端才能从"被动查杀"升级为"主动狩猎"——这才是现代终端安全的核心竞争力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。