macOS 下彻底卸载 aTrust:审计 → 隔离 → 移除 → 验证(含一键脚本)

目标:在  macOS  上把 aTrust(Sangfor/深信服零信任客户端)完全清理,并且全程   可回滚、可复核、最小破坏。文末附两段可直接使用的脚本:audit-atrust.sh(只读审计)与  purge-atrust-safe.sh(带隔离区、支持预演的安全卸载)。


为什么常规卸载不干净?

aTrust 通常不仅有  /Applications  下的 App,还会下发:

  • launchd  自启动(/Library/LaunchDaemons/Library/LaunchAgents
  • 提权 Helper(/Library/PrivilegedHelperTools
  • 程序/数据库/日志(/Library/Application Support/...~/Library/...
  • (可能存在的)系统/网络扩展、网络过滤器、证书、安装收据、描述文件等

因此仅“拖进废纸篓”无法清理干净,甚至可能被残留服务拉起再生。


安全策略(强烈建议遵循)

  1. 先审计,后执行:先“只读”定位所有痕迹。
  2. 隔离而非删除:把命中内容移动到  隔离区/Users/Shared/atrust_quarantine_时间戳/),确认无误再删除。
  3. 分阶段:先停服务 → 再移除文件 → 最后复查与重启。
  4. 最小匹配:仅匹配  atrust/sangfor  前缀,避免误伤。
  5. 可选:做一次  Time Machine  或快照备份。

步骤总览

  1. 只读审计(不会改动系统)
  2. 停用并隔离 launchd(系统级/用户级)
  3. 隔离主程序/数据/Helper(系统与用户目录)
  4. (UI)移除网络过滤器
  5. 卸载 System/Network Extension(如果存在)
  6. 删除证书(如果存在)
  7. 忘记安装收据(pkgutil)
  8. 移除描述文件(profiles)(如果存在)
  9. 深度扫描与重启验证

1)只读审计(脚本)

保存为  audit-atrust.sh,执行:bash audit-atrust.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/bin/bash
set -euo pipefail

echo "== 基本信息 =="; sw_vers; id; echo

echo "== 进程检查(不杀进程) =="
ps -axo pid,uid,command | egrep -i 'atrust|sangfor' || echo "无相关进程"; echo

echo "== launchd 自启动(系统/用户) =="
launchctl list | egrep -i 'sangfor|atrust' || echo "用户级无相关"
sudo launchctl list | egrep -i 'sangfor|atrust' || echo "系统级无相关"; echo

echo "== System/Network Extensions =="
systemextensionsctl list | egrep -i 'sangfor|atrust' || echo "无相关扩展"; echo

echo "== pkg 收据(安装记录) =="
pkgutil --pkgs | egrep -i 'sangfor|atrust' || echo "无相关收据"; echo

echo "== 常见落点(不改动) =="
for p in \
/Applications/aTrust.app \
/Library/Sangfor \
/Library/Application\ Support/Sangfor \
"$HOME"/Library/Application\ Support/Sangfor \
/Library/PrivilegedHelperTools/com.sangfor.* \
/Library/LaunchDaemons/com.sangfor.* \
/Library/LaunchAgents/com.sangfor.* \
"$HOME"/Library/LaunchAgents/com.sangfor.* \
/Library/Preferences/com.sangfor.* \
"$HOME"/Library/Preferences/com.sangfor.* \
/Library/Logs/Sangfor \
"$HOME"/Library/Logs/Sangfor \
"$HOME"/Library/Caches/com.sangfor.* ; do
[ -e "$p" ] && echo "[FOUND] $p"
done; echo

echo "== 证书(只列出,不删除) =="
sudo security find-certificate -a -Z -c "Sangfor" /Library/Keychains/System.keychain 2>/dev/null \
| sed -n 's/^ *SHA-1 hash: //p' | awk '{print "证书SHA1: "$0}'; echo

echo "== 描述文件(若公司推送) =="
sudo profiles list 2>/dev/null | egrep -i 'sangfor|atrust' || echo "无相关描述文件"; echo

echo "== Spotlight/局部搜索(只读) =="; echo "Spotlight:"
mdfind 'kMDItemFSName == "*atrust*"c || kMDItemFSName == "*sangfor*"c' || true

echo; echo "find(只扫常见目录):"
sudo find /Applications /Library "$HOME"/Library -maxdepth 4 -iname '*atrust*' -o -iname '*sangfor*' 2>/dev/null || true

echo; echo "审计完成:未对系统做任何改动。"


2)停用并隔离 launchd

先  bootout(卸载服务),再把  .plist  移动到隔离区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 隔离区
QDIR="/Users/Shared/atrust_quarantine_$(date +%Y%m%d_%H%M%S)"; sudo mkdir -p "$QDIR"

# 停系统级 Daemons(示例:按需补充)
for f in \
/Library/LaunchDaemons/com.sangfor.aTrustDaemon.plist \
/Library/LaunchDaemons/com.sangfor.aTrustTunnelWatchDog.plist \
/Library/LaunchDaemons/com.sangfor.aTrustTunnel.plist \
/Library/LaunchDaemons/com.sangfor.aTrustUninstallMonitor.plist \
/Library/LaunchDaemons/com.sangfor.auem.sfservice.plist \
/Library/LaunchDaemons/com.sangfor.limit.maxfiles.plist \
/Library/LaunchDaemons/com.sangfor.eaio.service.plist; do
[ -f "$f" ] && { sudo launchctl bootout system "$f" 2>/dev/null || true; }
done

# 停系统级/用户级 LaunchAgents(如存在)
launchctl bootout "gui/$(id -u)" /Library/LaunchAgents/com.sangfor.aTrustCore.plist 2>/dev/null || true

# 移到隔离区
sudo mv /Library/LaunchDaemons/com.sangfor.* "$QDIR"/ 2>/dev/null || true
sudo mv /Library/LaunchAgents/com.sangfor.* "$QDIR"/ 2>/dev/null || true

说明:zsh 若报  no matches found,是通配符未命中导致,给通配符加引号或改用  find  即可。


3)隔离主程序/数据/Helper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 系统级
sudo mv \
"/Applications/aTrust.app" \
"/Library/Application Support/Sangfor" \
"/Library/Sangfor" \
"/Library/PrivilegedHelperTools/com.sangfor.*" \
"$QDIR"/ 2>/dev/null || true

# 用户级(当前用户)
mv \
"$HOME/Library/Application Support/aTrust" \
"$HOME/Library/Application Support/aTrustTray" \
"$HOME/Library/Application Support/CrashReporter"/aTrust_*.plist \
"$HOME/Library/Preferences/com.electron.atrust.plist" \
"$HOME/Library/Preferences/com.sangfor.SFUemGuider.plist" \
"$QDIR"/ 2>/dev/null || true


4)网络过滤器(UI)

系统设置 → 网络 → 过滤器(Filters):找到  Sangfor/aTrust停用/删除


5)System/Network Extension(如存在)

1
2
3
4
5
6
# 只读列出
systemextensionsctl list | egrep -i 'sangfor|atrust' || echo "无相关扩展"

# 若有:把 <TEAMID> 与 <BUNDLEID> 替换为上一步输出
sudo systemextensionsctl uninstall <TEAMID> <BUNDLEID>

若提示需要关闭  SIP:进入恢复模式临时  csrutil disable → 卸载 → 再  csrutil enable  恢复开启。


6)证书(如存在)

1
2
3
4
5
6
7
# 列出 SHA1(只读)
sudo security find-certificate -a -Z -c "Sangfor" /Library/Keychains/System.keychain \
| sed -n 's/^ *SHA-1 hash: //p'

# 删除(逐个按 SHA1)
sudo security delete-certificate -Z <SHA1> /Library/Keychains/System.keychain

也可在“钥匙串访问”中搜索  Sangfor/深信服  删除。


7)pkg 安装收据

1
2
3
4
5
pkgutil --pkgs | egrep -i 'sangfor|atrust' || echo "无相关收据"
for id in $(pkgutil --pkgs | egrep -i 'sangfor|atrust'); do
sudo pkgutil --forget "$id" || true
done


8)配置描述文件(如公司推送)

1
2
3
4
sudo profiles list | egrep -i 'sangfor|atrust' || echo "无相关描述文件"
# 若有,按 Identifier 删除
sudo profiles remove -identifier <Identifier>


9)深度扫描与重启验证

1
2
3
4
5
6
7
8
9
10
11
# 深度扫描(应只剩隔离区)
sudo find /Library /Applications "$HOME"/Library -maxdepth 4 \
-iname '*sangfor*' -o -iname '*atrust*' 2>/dev/null

# 重启后快速核验(都应无残留或仅 grep 自身)
launchctl list | egrep -i 'sangfor|atrust' || echo "user launchd: 无残留"
sudo launchctl list | egrep -i 'sangfor|atrust' || echo "system launchd: 无残留"
systemextensionsctl list | egrep -i 'sangfor|atrust' || echo "无扩展残留"
pkgutil --pkgs | egrep -i 'sangfor|atrust' || echo "无收据残留"
ps -axo pid,uid,command | egrep -i 'atrust|sangfor' || echo "无进程残留"


安全版一键脚本(支持预演/可回滚)

purge-atrust-safe.sh:默认  DRYRUN  预演;传  –apply  才真正执行。所有删改都移动到隔离区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/bin/bash
set -euo pipefail
APPLY=0
[ "${1:-}" = "--apply" ] && APPLY=1
QDIR="/Users/Shared/atrust_quarantine_$(date +%Y%m%d_%H%M%S)"
LOG="$QDIR/uninstall.log"

say(){ echo "$@"; }
doit(){ if [ $APPLY -eq 1 ]; then eval "$@"; else echo "[DRYRUN] $@"; fi; }
moveq(){ local p="$1"; [ -e "$p" ] || return 0; local b; b=$(basename "$p"); doit "sudo mkdir -p \"$QDIR/files\""; doit "sudo mv \"$p\" \"$QDIR/files/${b}_$(date +%H%M%S)\""; }

say "=== 模式: $([ $APPLY -eq 1 ] && echo 执行 || echo 预演) ==="
doit "sudo mkdir -p \"$QDIR\" && echo '开始 '$(date) | sudo tee -a \"$LOG\" >/dev/null"

say "1) 终止相关进程"; doit "sudo pkill -if '(atrust|sangfor)' || true"

say "2) bootout & 隔离 launchd"
for f in /Library/LaunchDaemons/com.sangfor.* /Library/LaunchAgents/com.sangfor.* "$HOME"/Library/LaunchAgents/com.sangfor.*; do
if [ -f "$f" ]; then
case "$f" in
/Library/LaunchDaemons/*|/Library/LaunchAgents/*) doit "sudo launchctl bootout system \"$f\" 2>/dev/null || true";;
"$HOME"/Library/LaunchAgents/*) doit "launchctl bootout gui/$(id -u) \"$f\" 2>/dev/null || true";;
esac
moveq "$f"
fi
done

say "3) 隔离程序/数据/Helper"
for p in \
/Applications/aTrust.app \
/Library/Sangfor \
/Library/Application\ Support/Sangfor \
"$HOME"/Library/Application\ Support/Sangfor \
/Library/PrivilegedHelperTools/com.sangfor.* \
/Library/Preferences/com.sangfor.* \
"$HOME"/Library/Preferences/com.sangfor.* \
/Library/Logs/Sangfor \
"$HOME"/Library/Logs/Sangfor \
"$HOME"/Library/Caches/com.sangfor.* \
"$HOME"/Library/Application\ Support/aTrust \
"$HOME"/Library/Application\ Support/aTrustTray \
"$HOME"/Library/Application\ Support/CrashReporter/aTrust_*.plist; do
[ -e "$p" ] && moveq "$p"
done

say "4) pkg 收据(forget,不删文件)"
PKGS=$(pkgutil --pkgs | egrep -i 'sangfor|atrust' || true)
if [ -n "$PKGS" ]; then
while IFS= read -r id; do [ -n "$id" ] && doit "sudo pkgutil --forget \"$id\""; done <<< "$PKGS"
fi

say "5) System Extensions(仅提示)"; /usr/bin/systemextensionsctl list | /usr/bin/egrep -i 'sangfor|atrust' || true
say " 使用:sudo systemextensionsctl uninstall <TEAMID> <BUNDLEID>"

say "6) 证书(只列出)"
CERTS=$(sudo security find-certificate -a -Z -c "Sangfor" /Library/Keychains/System.keychain 2>/dev/null | sed -n 's/^ *SHA-1 hash: //p' || true)
[ -n "$CERTS" ] && { echo "$CERTS" | sed 's/^/ SHA1: /'; echo "删除示例:sudo security delete-certificate -Z <SHA1> /Library/Keychains/System.keychain"; }

say "7) 提示:系统设置→网络→过滤器 删除/停用 Sangfor/aTrust"
say "8) 重启后复查:应无进程/launchd/扩展再生"

doit "echo '结束 '$(date) | sudo tee -a \"$LOG\" >/dev/null"; say "隔离与日志目录:$QDIR"
[ $APPLY -eq 0 ] && say "当前为预演。真正执行:bash $(basename "$0") --apply"


本次案例要点(供对照)

  • 发现并隔离:多个  系统级 LaunchDaemons/LaunchAgent/Library/PrivilegedHelperTools/com.sangfor.*
  • System Extension / 证书:未发现
  • pkg 收据com.mygreatcompany.pkg.aTrust → 已  pkgutil --forget
  • 用户级残留~/Library/Application Support/aTrust*、日志/Crash/偏好等 → 全部移入隔离区
  • 重启后核验:launchd / systemextensionsctl / pkgutil / ps  全部  无残留

重新安装:最小影响的做法

  1. 仅用  官方/企业 IT 门户  提供的安装包;安装前可用  spctl/codesign  校验(应  accepted  且 TeamID 明确)。

  2. 安装后立刻检查:

    • 系统设置 → 网络 → 过滤器:仅在需要连接时开启;平时关闭。
    • 系统设置 → 通用 → 登录项:关闭 aTrust/Sangfor 的“允许在后台运行”(不违背公司策略前提下)。
  3. 首次运行后自检:

    1
    2
    3
    launchctl list | egrep -i 'sangfor|atrust' || true
    systemextensionsctl list | egrep -i 'sangfor|atrust' || true

  4. 稳定使用一段时间后,再删除隔离区:sudo rm -rf /Users/Shared/atrust_quarantine_*


常见坑位

  • zsh 报 no matches found:通配符未命中;用引号包裹或改用  find
  • SIP 阻挡卸载 System Extension:按需在恢复模式临时  csrutil disable,卸完后务必  csrutil enable
  • 网络过滤器  需要在  系统设置 UI  移除,命令行不一定有同等能力。
  • 残留再生  多来自未停用的  launchd  或扩展,务必先  bootout  再移动文件。

至此,aTrust 即可在 macOS 上实现“可审计、可回滚、无残留”的彻底卸载。