远程控制工具痕迹

前面几篇的排查顺序都是从文件系统和日志入手。但有一类痕迹,它的起点不在磁盘上,而在网络连接表里。

最后更新 2026-10-09版本 v1.0维护 DigiForensics查看历史 0

关键词:远程控制工具、AnyDesk、TeamViewer、向日葵、ToDesk、RustDesk、外联分析、systemd 服务、配置凭据痕迹 难度:进阶 前置知识:Linux 目录结构与 systemd、TCP/IP 连接状态与端口、磁盘镜像与哈希校验、ext4 时间戳概念

一、概述

前面几篇的排查顺序都是从文件系统和日志入手。但有一类痕迹,它的起点不在磁盘上,而在网络连接表里。

攻击者拿下服务器后通常不会只留一个 WebShell——它随时可能被清理。更稳妥的做法是装一个商用远控工具:向日葵、ToDesk、AnyDesk、TeamViewer、RustDesk。

这些工具合法、签名完整、主流杀软不拦截,因为它们本来就是给企业远程运维用的。

于是现实很尴尬:发现 anydesk 进程,你无法仅凭这一点判断它是运维装的还是攻击者装的。

本篇要回答的是:它什么时候、通过什么方式被装上的?谁在用它、连过哪些 IP?用过它传输了什么?

答案几乎都藏在「外联」里。

WebShell 是被动等待(你来请求我,我执行)。远控工具是主动外联(我主动连到厂商调度服务器注册上线,等你来找我)。这条主动外联在连接表、/var/log/ 里的陌生 IP、systemd 服务配置中留下大量痕迹。

这就是本篇的骨架:先看外联、再反查软件。

理由很直接——磁盘上可能有几十万个文件,靠文件名匹配工具名会淹没在噪声里;而连接表里「一个非业务进程正连向某陌生公网 IP」这样的记录通常只有个位数。从连接反查进程、从进程反查文件,命中率远高于全盘 grep。

在取证流程中的位置:本篇属于分析环的归因层,是前面几篇(站点、WebShell、面板)之后的自然延伸——当站点侧查不出足够证据、但服务器确实被控时,远控痕迹是常见的那条出路。它同时是一条横向扩线通道:Xshell / FinalShell 的会话配置里存着运维连接过的所有主机清单,WinSCP 与 Navicat 的配置里存着数据库连接信息,这些本身就是被控主机群的资产清单。 涉及的证据形态(没有一条命令能覆盖全部,必须分工具查):

工具 Linux 侧关键痕迹 高价值字段
AnyDesk /root/.anydesk/、/etc/anydesk/、systemd unit ** TrustedDevices(免交互放行的设备)**、证书与私钥、无人值守口令哈希与盐
TeamViewer /etc/teamviewer/、~/.config/teamviewer/ ** Connections_incoming.txt(成功接入记录:对方 ID、账号名、起止时间)**
向日葵 /usr/local/sunlogin/、systemd 服务 ** fastcode(识别码)、本机验证码加密值、历史连接记录**
ToDesk /usr/local/bin/todesk*、/etc/systemd/system/todesk*.service ** config.ini 的临时授权码**
RustDesk ~/.config/RustDesk/、systemd 服务 ** relay-server 指向自建实例 = 非官方部署**
Xshell / FinalShell ~/.Xshell/Xshell/Sessions/*.xsh、~/.finalshell/conn/ ** HostName/UserName/Port 明文;password 密文**
Navicat —(Windows 为主) 连接配置与加密口令
RDP/3389 auth.log 的 rdesktop/xfreerdp、.bash_history 会话时长、来源 IP

⚠️ 必须逐条核对实际环境:TeamViewer 的 QuickSupport 把日志写在 %APPDATA% 而非 %ProgramFiles%;向日葵安装版 / 绿色版 / 简约版对应不同注册表子键;RustDesk 配置目录名大小写在版本间不一致。不确定时以「两处都查」为准。

外联痕迹为什么是最强的一条线:

现象 为什么能取证 强度
出站连接目的地陌生 业务服务器不该持续连陌生公网 IP 强(需排除更新 / 云备份 / 监控)
连接长期存在(心跳) 无人操作时连接也在 强
打洞成功时目的地是攻击者真实 IP 中继是遮蔽,直连是暴露 ★ 极强
配置里的服务器地址可反查 自建或被改配置的实例指向非厂商地址 强

第 3 条最实用:**中继隐藏攻击者 IP,直连把它写进连接表。**遇到直连基本可直接锁定攻击者出口 IP。

但出站连接不能孤立看——正常服务器也连更新源与监控,判据是「发起连接的进程是什么」。

定性的门槛(本篇反复强调的一条):远控痕迹的定性门槛远高于普通后门。

发现工具 ≠ 认定攻击。判定为攻击者所留需要时间、IP、配置三项交叉,缺任何一项都只能写成「存在远控软件,用途待定」。

**这类误判的代价在实务中特别高。**把运维的正常工具写成攻击者痕迹,会让委托方的调查方向整个跑偏。

能回答什么:某个远控工具什么时候被装上、连过哪些 IP、有没有成功的接入记录(对方 ID、账号、起止时间)、配置指向的是官方还是自建实例、运维曾经连接过哪些主机。

不能回答什么:远控会话里具体传输了什么文件(工具本身不记录,要看流量或终端侧的痕迹);发现工具不等于认定攻击;配置里的口令能否还原(只做离线分析,报告中脱敏);工具被卸载后的痕迹(需靠文件系统残留与内存侧)。

内容边界(本篇的红线):本文用于已获得合法授权的取证场景,目标只有发现痕迹、固定证据两项,不提供任何安装、隐藏或绕过检测的方法。

读到无人值守口令哈希、可信设备列表、临时授权码等材料时:只做离线分析与风险评估,不做在线验证、不尝试连接。

报告中一律脱敏,涉密材料按密级登记管理。

读者前提:需要 Linux 的网络与进程排查基础(见 Linux 系统日志深度分析 与 proc 虚拟文件系统取证)、取证工件地图(Linux 侧) 的痕迹体系,以及基本的 systemd 与配置目录认知。建议先按 3.1 第 1 步 做外联排查——本篇的顺序不可颠倒。

二、核心原理

2.1 为什么「外联」是最好的切入点

被控端(服务器)                        主控端(攻击者)
  ① 启动:主动连厂商调度服务器注册上线 → 出站 TCP 长连接
  ② 会话:双向流量(画面、键鼠);NAT 打洞失败则经厂商中继转发
  ③ 心跳:周期性报到 → 连接长期存在,不会只连一次
现象 为什么能取证 强度
出站连接目的地陌生 业务服务器不该持续连陌生公网 IP 强(需排除更新/云备份/监控)
连接长期存在(心跳) 无人操作时连接也在 强
打洞成功时目的地是攻击者真实 IP 打洞失败才走中继;中继是遮蔽,直连是暴露 ★ 极强
配置里的服务器地址可反查 自建或被改配置的实例指向非厂商地址 强

第 3 条最实用:**中继隐藏攻击者 IP,直连把它写进连接表。**遇到直连基本可直接锁定攻击者出口 IP。

但出站连接不能孤立看——正常服务器也连更新源与监控,判据是「发起连接的进程是什么」。ss -tunap 直接显示进程名与 PID,这是连接表最有价值的部分。

2.2 各工具的痕迹落点对照

没有一条命令能覆盖全部,必须分工具查:

工具 Windows 侧关键痕迹 Linux 侧关键痕迹 高价值字段
AnyDesk %APPDATA%\AnyDesk\(service.conf、system.conf、ad.trace、connection_trace.txt、thumbnails\)、%ProgramData%\AnyDesk\ /root/.anydesk/、/etc/anydesk/、systemd unit ** ad.anynet.pwd_hash/pwd_salt(无人值守口令哈希与盐)、ad.anynet.cert/pkey(证书与私钥)、TrustedDevices(可信设备列表——免交互放行的设备)**
TeamViewer %APPDATA%\TeamViewer\(Connections.txt、LogFiles\(连接历史日志,含远端 IP 与操作时长)、TrustedDevices、凭据管理器中的 Credentials 加密 blob)、%ProgramFiles%\TeamViewer\(Connections_incoming.txt、TeamViewer*_Logfile*.log) /etc/teamviewer/、~/.config/teamviewer/ ** Connections_incoming.txt(成功接入记录:对方 ID、账号名、起止时间、类型)、Connections.txt(连出记录)、LogFiles\ 中逐条会话日志**
向日葵 新版走注册表:HKCU/HKLM\Software\Oray\SunLogin\SunloginClient(SunloginInfo/SunloginGreenInfo/SunloginLiteInfo 三个子键) /usr/local/sunlogin/、systemd 服务 ** encry_pwd(本机验证码加密值)、fastcode(识别码)、历史连接记录**
ToDesk %APPDATA%\ToDesk\(以 config.ini 为主) /usr/local/bin/todesk*、/etc/systemd/system/todesk*.service ** config.ini 的 tempAuthPassex(临时授权码)**
RustDesk %APPDATA%\RustDesk\(RustDesk.toml/RustDesk2.toml) ~/.config/RustDesk/、systemd 服务 ** key(中继密钥)、custom-rendezvous-server、relay-server——指向自建实例**
NetSarang WinSCP:HKCU\Software\Martin Prikryl\WinSCP 2\Sessions(旧版 Password 字段为 Base64 编码,可还原;新版改用 AES 加密);Xshell:%APPDATA%\NetSarang\Xshell\Sessions\*.xsh ~/.Xshell/Xshell/Sessions/*.xsh ** .xsh 中 HostName/UserName/Port 明文;口令加密(WinSCP 新版为 AES)**
FinalShell %LOCALAPPDATA%\finalshell\conn\*.json、程序同级 finalShell_data ~/.finalshell/conn/、程序同级 finalShell_data ** host/userName/port 明文;password 密文**
Navicat %APPDATA%\Navicat\MySQL\*.tncf(连接信息文件)、较新版 %APPDATA%\PremiumSoft\Navicat*\Profiles\*.ncx、旧版 HKCU\Software\PremiumSoft\Navicat*\Servers — 连接配置与加密口令
MRemote %APPDATA%\MRemote\ — 连接配置与可能的凭据
RDP/3389 TerminalServices-*.evtx auth.log 的 rdesktop/xfreerdp、.bash_history 的 mstsc 会话时长、来源 IP、登录类型

⚠️ 必须逐条核对实际环境:TeamViewer 的 QuickSupport 把日志写在 %APPDATA% 而非 %ProgramFiles%;向日葵安装版/绿色版/简约版对应不同注册表子键;RustDesk 配置目录名大小写在版本间不一致。不确定时以「两处都查」为准。

2.3 Linux 侧的四个落点

① 安装:/usr/bin/、/usr/local/bin/、/opt/ 下的可执行文件与自解压残留(*.AppImage、setup.sh),证明装过,配合 mtime 定时点。 ② 持久化:/etc/systemd/system/*.service、/etc/rc.d/init.d/,unit 内可含明文 Password=,价值最高。 ③ 配置凭据:~/.config/<工具>/、/etc/<工具>/ 下的 service.conf、.toml,含中继地址、本机 ID、口令哈希。 ④ 运行外联:/tmp、/var/tmp 的自解压残留与 ss 中的常驻出站连接,给出时间线与攻击者 IP。

第 ② 类是重点。

不少远控在 Linux 上以 systemd 服务安装,服务定义文件里可能直接写有 Password=。其性质与 /etc/shadow 的哈希不同——可能明文,也可能弱加密,取决于工具与版本。

取证动作是:找到 unit → 确认 ExecStart 确属该工具 → 检查 Password= → 记录「存在性」与「长度指纹」(明文脱敏)→ 固定并哈希。

这是本篇合规要求最严格的一步:发现的凭据只用于证明「该机存在无人值守访问配置」,绝对不可拿去尝试连接。

配置里的中继地址、本机 ID 与口令哈希如何固定,以及判断「解不开」时该停在哪一步,见第 06 篇。

2.4 为什么不重复 Linux 通用痕迹

账户、SUID、crtime、时间线重建见《Linux 取证 — 取证工件地图》;服务器现场抢占式固定见第 01 篇;面板侧攻击路径见第 02 篇。本篇只做一件事:识别远控工具痕迹并评估影响面。


三、操作步骤

3.1 第 1 步:外联排查(第一优先级)

价值最高,因为它不依赖「你知道要找什么工具」。ss -tunap 输出示例(列:Netid State Recv-Q Send-Q Local Peer Process):

tcp  ESTAB 0 0 10.0.1.5:41234 203.0.113.77:32899 users:(("anydesk",pid=2871,fd=9))
#                                                  ↑ 陌生进程 + 陌生 IP,双重信号

判读规则:进程持续连向陌生公网 IP 且非业务程序 → 远控外联(强);目的地是厂商中继域名/IP → 合法远控工具(中,仍可能被攻击者滥用);监听端口对应陌生进程 → 远控在等待连接(强);出站目的地是内网其他主机高端口 → 横向移动或隧道(中)。

重要边界:ss 只反映采集时刻。心跳可能只持续几秒,也可能恰好空闲。「没查到」≠「不存在」——这正是必须反查软件痕迹的理由。

离线镜像侧的替代入口(在线抢不到就用日志):把 /var/log/ 里的陌生 IP 汇总出来,再回连接与工具痕迹。

grep -rhoE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' /mnt/df/var/log/auth.log /mnt/df/var/log/secure \
  /mnt/df/var/log/wtmp 2>/dev/null | sort | uniq -c | sort -rn | head -30
awk '$9 !~ /^\./ {print $1, $3, $9, $11}' /mnt/df/var/log/auth.log 2>/dev/null | sort -u | head -30
journalctl --directory=/mnt/df/var/log/journal -u anydesk --no-pager 2>/dev/null | head -40
journalctl --directory=/mnt/df/var/log/journal -u todesk -n 50 --no-pager 2>/dev/null

3.2 第 2、3 步:进程、服务与软件痕迹

ps auxwwf | grep -viE 'grep|\[' | head -40
ps -eo pid,ppid,user,lstart,etime,cmd --sort=-etime | head -30   # 按启动时长排序
systemctl list-unit-files --state=enabled | grep -iE 'todesk|anydesk|sunlogin|rustdesk|teamviewer|xrdp'
systemctl list-units --type=service --state=running | grep -iE 'todesk|anydesk|sunlogin|rustdesk|teamviewer|xrdp'
ls -l /mnt/df/etc/systemd/system/*.service 2>/dev/null          # 离线镜像更可靠
find /mnt/df -xdev \( -iname '*todesk*' -o -iname '*anydesk*' -o -iname '*sunlogin*' \
  -o -iname '*rustdesk*' -o -iname '*teamviewer*' -o -iname '*finalshell*' \) 2>/dev/null
ls -l /mnt/df/usr/bin/*desk* /mnt/df/usr/local/bin/*desk* /mnt/df/opt/ 2>/dev/null   # 安装目录
ls -l /mnt/df/etc/rc.d/init.d/ /mnt/df/etc/init.d/ /mnt/df/etc/xdg/autostart/ 2>/dev/null
ls -l /mnt/df/root/.xsession /mnt/df/root/.xsessionrc /mnt/df/home/*/.xsession 2>/dev/null
find /mnt/df -xdev -name '*.desktop' -path '*autostart*' 2>/dev/null             # 桌面自启动
find /mnt/df/tmp /mnt/df/var/tmp -type f \( -name '*.ko' -o -name '*.bin' -o -name '*.AppImage' \) 2>/dev/null | head
ls -l /mnt/df/root/.anydesk/ /mnt/df/etc/anydesk/ 2>/dev/null   # 配置目录(RustDesk 大小写两处都查)
grep -iE 'todesk|anydesk|sunlogin|rustdesk|teamviewer|finalshell' \
  /mnt/df/root/.bash_history /mnt/df/home/*/.bash_history 2>/dev/null

3.3 第 4 步:读取配置、提取外联线索

优先读三类字段:服务器/中继地址、本机 ID、连接记录。首个命令是高价值项,务必执行;AnyDesk 各文件逐个读取的命令见速查第 5 组。

grep -rn -iE '^[[:space:]]*(Password|Passwd)[[:space:]]*=' \
  /mnt/df/etc/systemd/system/ /mnt/df/lib/systemd/system/ 2>/dev/null
cat /mnt/df/root/.config/RustDesk/RustDesk.toml 2>/dev/null
strings /mnt/df/opt/xxx/bin/xxx 2>/dev/null | grep -iE 'server|host|port|token|password|domain' | head -20

必查字段:ad.anynet.pwd_hash / pwd_salt(无人值守口令哈希与盐,AnyDesk system.conf)、ad.anynet.cert / pkey(本机证书与私钥,等同设备身份,切勿外传)、client-id / id(本机唯一 ID,即「门牌号」)、key(自建 RustDesk 服务器共享密钥)、custom-rendezvous-server / relay-server(非厂商地址 → 可能被改造或自建)、tempAuthPassex(ToDesk 临时授权码)、encry_pwd(向日葵本机验证码加密值)、HostName / UserName / Port(Xshell .xsh 与 FinalShell .json 中的目标资产清单,通常明文)。

⚠️ 两条硬性规定:① 凭据字段一律脱敏,报告只写「已发现该字段,值见密级附件」;② 发现后立即登记移交,不得私留、不得用于任何连接尝试。

3.4 第 5 步:批量扫描脚本

**仅依赖 Python 3.6+ 标准库,只读取不执行被检文件。**已实测:路径命中、键值抽取、明文口令检测、敏感字段脱敏、空目录与非法参数处理均正常。运行 python3 rtscan.py /mnt/df > /evidence/rtscan-report.csv 后,输出(列:category artifact_path size mtime kind detail):

remote-admin-tool,etc/systemd/system/anydesk.service,141,2024-03-18 01:12:33,plaintext-cred,systemd/init 含明文口令字段(已脱敏)
remote-admin-tool,root/.anydesk/service.conf,121,2024-03-18 01:10:07,server-addr,命中IP=198.51.100.9; ... | client-id=123456789 | direct-access=198.51.100.9
remote-admin-tool,root/.config/RustDesk/RustDesk.toml,139,2024-03-18 01:11:55,server-addr,... | server=rs-ny.example.com | serial=2091837408 | key=Ab************Op

已知局限(必须写进报告,否则 CSV 会被误读):只按关键字匹配路径名,无特征文件名会漏;只解析明文键值对,不解析二进制加密凭据;敏感字段已

安全验证 当前请求需要先完成一次滑块验证。