前面幾天,我們已經讓 BehaviorGuard 具備 Process、Network、File、Persistence 等行為偵測能力,並逐步完成 Correlation Engine(事件關聯引擎)、Response Policy(回應政策)與受控環境的自動化回應。
但一套資安監控系統,不是能夠偵測到攻擊就代表已經完善。
例如:
這些問題都與 Reliability(可靠性) 有關。
因此,Day28 的目標不是增加更多偵測規則,而是改善 BehaviorGuard 原本已經存在的功能,並透過實際實驗找出限制。
在 File Monitor 中,Watchdog 會持續監控檔案系統事件。
例如建立一個檔案:
echo "Hello" > test.txt
Linux 可能產生多個事件:
CREATED
MODIFIED
這並不一定代表發生了兩次獨立的安全事件。
如果每次異動都直接產生 Alert,SOC(Security Operations Center,資安監控中心)分析人員可能收到大量重複告警。
因此,我們改進了 detection/alert_manager.py,利用以下資訊判斷是否需要抑制重複告警:
目前使用的告警抑制窗口為 5 秒。
例如同一個檔案,短時間內觸發相同規則,而且 SHA256 沒有改變,就可以避免重複發出內容相同的告警。
不過,若檔案內容改變,SHA256 也會改變,系統就應該允許新的告警。
另外,針對 BG-FILE-005(Executable Permission Added),還必須考慮檔案權限。
原因是:
chmod +x test.sh
只改變檔案的執行權限,不一定會改變 SHA256。
如果只比較內容雜湊,就可能忽略有安全意義的權限變動。
因此我們讓這條規則的去重判斷也包含 Mode 與 Executable 狀態。
這次完成了 9 項去重測試,並使用實際 Watchdog 事件驗證相同內容抑制、不同內容重新告警的情況。
Alert Deduplication(告警去重)不是單純刪除重複紀錄,而是判斷兩個事件是否具有相同的安全意義。
設計不良的去重機制可能減少告警數量,卻同時造成真正重要的事件被忽略。
BehaviorGuard 已經可以把不同來源的安全事件整合分析。
例如:
可疑 Python 執行
↓
可疑腳本檔案建立
↓
Persistence 行為
↓
Correlation Engine
↓
判斷是否形成可疑行為鏈
在 Day28,我們針對 BG-CORR-006 進行誤報控制測試,確認事件關聯不應只因為幾種 Alert 同時存在就直接成立。
目前的實作會利用 UID、關聯腳本路徑與時間窗口等條件進行比對。
例如,兩個事件雖然發生在同一段時間,但如果 UID 或腳本路徑不同,就不應輕易認定是同一條攻擊鏈。
這次完成了 7 項相關測試,包括不同 UID、不同路徑、事件順序與重複觸發等情況。
此外,我們也重新測試自動化回應政策。
目前 BehaviorGuard 對一般即時偵測事件採取保守方式:
Detection
↓
Alert
↓
Response Policy
↓
DRY_RUN / REVIEW_REQUIRED
↓
Audit Log
其中:
這次 10 項 Response Policy 測試通過,包括無效 PID、無效目標與受控實驗條件的檢查。
需要特別區分的是,BehaviorGuard 仍保留先前設計的受控實驗回應功能;經過登錄與授權的實驗目標,可以依據對應政策執行特定回應。因此不能把一般 Live Alert 的 DRY_RUN 直接解讀成所有情境下都不可能有實際動作。
EDR 的 Response 不只是「偵測到就封鎖」。
真正需要考慮的是:
這筆告警是否有足夠的證據?執行動作是否安全?如果誤判,會不會影響正常業務?
原本 BehaviorGuard 同時執行三個監控器:
BehaviorGuard Agent
│
├── Process Monitor
├── Network Monitor
└── File Monitor
但是三個監控器的停止方式並不一致。
Process 與 Network Monitor 使用:
while True:
...
time.sleep(1)
而主程式主要依靠 daemon thread 隨 Python 行程結束。
這種方式雖然可以停止程式,但不是完整的執行緒生命週期管理。
因此,我們加入 threading.Event() 作為統一停止訊號。
概念如下:
stop_event = threading.Event()
監控器改成:
while not stop_event.is_set():
# 執行監控
stop_event.wait(1)
當使用者按下 Ctrl+C:
stop_event.set()
三個監控器收到停止訊號後,便能離開各自的監控迴圈。
主程式再使用 join() 等待執行緒結束。
[INFO] Shutdown requested
[STOP] Process Monitor
[STOP] Network Monitor
[STOP] File Monitor
[PASS] All three monitor threads stopped
[INFO] BehaviorGuard shutdown complete
這次測試證明,三個監控執行緒在正常操作下都能收到停止訊號並完成關閉。
這部分其實花了不少時間除錯。
最初使用字串比對方式自動修改 Python 函式,結果因為函式格式與預期不同,出現:
RuntimeError: Monitor function boundaries not found
後來又遇到:
RuntimeError: Unexpected function signature
最後改為利用 Python AST(Abstract Syntax Tree,抽象語法樹)定位函式,並讓修改腳本能辨識原本是否已經加入 stop_event,才成功完成升級。
另外,由於 Kali 預設使用 zsh,而 status 是 zsh 的特殊唯讀參數,所以使用:
status=$?
會出現:
zsh: read-only variable: status
之後改用其他變數名稱才解決。
這讓我學到,維護程式時不能只考慮功能本身,也要注意不同 Shell、原始程式版本與自動修改工具的可靠性。
原本 Network Monitor 使用兩個集合:
seen_connections = set()
seen_alerts = set()
用途是避免同一個網路連線持續被重複輸出。
但問題是,這些集合會持續累積已經看過的連線資訊。
如果同一個連線特徵消失後再次出現,舊機制可能還記得之前的狀態,造成新事件被忽略。
因此我們新增:
agent/network_state.py
並實作 NetworkStateTracker。
核心概念是每次取得新的網路快照,和前一次比較。
current = {
self.connection_key(event)
for event in events
}
new_keys = current - self.active
disappeared = self.active - current
其中:
current:這一次掃描看見的連線。self.active:上一輪記錄的連線。new_keys:這一次新出現的連線。disappeared:上一輪存在、這次消失的連線。這樣就能保留目前存在的連線狀態,而不是永久保存所有歷史連線。
我們使用 socat 建立本機 TCP Listener:
socat TCP-LISTEN:19999,bind=127.0.0.1,reuseaddr -
第一次啟動時,BehaviorGuard 偵測到:
Rule : BG-NET-001
Name : Suspicious Network Listener
Severity : HIGH
Process : socat
PID : 2942453
Local : 127.0.0.1:19999
接著關閉 socat,再次啟動相同 Port。
第二次結果:
Rule : BG-NET-001
Process : socat
PID : 2942806
Local : 127.0.0.1:19999
兩次都成功觸發 BG-NET-001,並產生不同的 Event ID。
而且這兩次事件的 Response Policy 都維持 DRY_RUN,沒有對測試程序執行未經批准的即時處置。
需要注意,兩次 PID 不同,因此這項實測證明新程序可以再次被偵測;相同 PID、相同連線特徵消失後重新出現的邏輯,則是透過 State Tracker 單元測試驗證。
原本 File Monitor 只能監控:
WATCH_PATH = "/tmp/behaviorguard_test"
這對初期開發很方便,但不利於後續擴充。
因此,我們加入環境變數 BG_WATCH_PATH,讓使用者能指定不同監控目錄。
例如:
BG_WATCH_PATH="$HOME/behaviorguard_day28_watch_test" \
PYTHONPATH=. python3 -u agent/behaviorguard.py
這樣就不需要每次都打開 Python 程式修改路徑。
同時,我們也加入路徑驗證,避免任意指定根目錄 / 或 /etc。
首先建立測試目錄:
mkdir -p ~/behaviorguard_day28_watch_test
啟動後,系統顯示:
[INFO] Watching: /home/kun/behaviorguard_day28_watch_test
接著建立測試檔案:
echo "Day28 file watch test" > ~/behaviorguard_day28_watch_test/test.txt
BehaviorGuard 成功收到:
Action : CREATED
File Name : test.txt
Extension : .txt
Hidden : False
Executable : False
Size : 22
UID : 1000
以及後續的:
Action : MODIFIED
Path : /home/kun/behaviorguard_day28_watch_test/test.txt
代表 File Monitor 已經能接收指定目錄的檔案事件。
但需要注意,目前新增的功能主要是監控範圍設定;原本部分 Detection Rules 仍針對固定實驗目錄進行路徑正規化,因此不能直接宣稱所有檔案規則都已完整支援任意監控目錄。
這是今天我覺得最有收穫的部分。
BehaviorGuard 的 Process Monitor 目前是透過 Linux /proc 取得程序資訊。
但它採用的是 Polling(輪詢),意思是每隔一段時間才檢查系統有哪些程序。
例如:
0.0 秒:程序開始執行
0.1 秒:程序已經結束
1.0 秒:Process Monitor 再次掃描
這樣就可能完全看不到剛才執行的程序。
我們最初測試不同存活時間的程序,竟然全部得到 100% 觀察率。
後來發現測試程式存在錯誤:使用了已經設定為 True 的 threading.Event.wait(),導致輪詢沒有按照預期等待。
換句話說,測試工具本身就有問題。
修正後,我們也排除 Zombie Process(殭屍程序)等可能造成誤判的因素。
重新測試,每組 10 次:
| 程序存活時間 | 觀察成功次數 | 觀察率 |
|---|---|---|
| 0.1 秒 | 3/10 | 30% |
| 0.3 秒 | 3/10 | 30% |
| 0.5 秒 | 6/10 | 60% |
| 1.0 秒 | 10/10 | 100% |
| 2.0 秒 | 10/10 | 100% |
這次結果顯示,程序存活時間越短,越有可能被輪詢方式漏掉。
假設攻擊者透過 Python 快速執行一段可疑程式。
即使我們寫了:
BG-CMD-002
Python Inline Code Execution
如果監控器根本沒看見這個 Python 程序,後面的 Detection Engine 就沒有事件可以分析。
這就是 Telemetry Gap(遙測資料缺口):不是規則無法辨識,而是規則沒有收到事件。
發現問題後,我們沒有直接認為 Polling 不可使用,而是先嘗試降低輪詢間隔。
原本的程式:
stop_event.wait(1)
改成:
stop_event.wait(get_process_interval())
再透過環境變數控制:
BG_PROCESS_INTERVAL=0.2 \
PYTHONPATH=. python3 -u agent/behaviorguard.py
這樣就可以讓 Process Monitor 每 0.2 秒檢查一次系統程序。
接著我們另外建立比較實驗,每個條件測試 15 次。
| 程序存活時間 | 1 秒輪詢 | 0.2 秒輪詢 |
|---|---|---|
| 0.1 秒 | 0% | 53.3% |
| 0.3 秒 | 20% | 100% |
| 0.5 秒 | 46.7% | 100% |
| 1.0 秒 | 100% | 100% |
我們可以觀察到,縮短輪詢間隔後,短暫程序的觀察率有明顯提升。
其中 0.3 秒程序的結果尤其明顯:
1.0 秒輪詢:3/15
0.2 秒輪詢:15/15
但 0.1 秒程序仍然只有 53.3% 的觀察率,代表即使增加輪詢頻率,短暫程序仍可能被漏掉。
另外,這次 Benchmark 使用獨立的 psutil 掃描執行緒,測試的是程序觀察能力,並不等於完整 BehaviorGuard 的端到端 Detection 成功率。
不一定。
當輪詢從每秒 1 次增加到每秒約 5 次,系統也需要更頻繁地收集程序資訊。
因此可能造成:
/proc 讀取。本次尚未量測 CPU 和記憶體消耗,所以還不能判定 0.2 秒就是最理想的監控間隔。
未來可以再研究 Linux Audit 或 eBPF 等事件式監控機制,減少對輪詢的依賴。
今天並沒有新增大量 Detection Rules,但改善了 BehaviorGuard 的可靠性。
| 改善項目 | 今日成果 |
|---|---|
| File Alert 去重 | 降低重複告警,加入權限變動考量 |
| Correlation 驗證 | 測試 UID、路徑、時間等關聯條件 |
| Response Safety | 驗證 DRY_RUN 與受控回應條件 |
| Graceful Shutdown | 三個 Monitor 可正常停止 |
| Network State Tracker | 追蹤連線出現、消失與再次出現 |
| File Monitor | 支援自訂監控目錄 |
| Process Monitor | 支援設定輪詢間隔 |
| Reliability Benchmark | 實測短暫程序觀察率 |
這些改善讓 BehaviorGuard 不再只是單純觸發規則的 Python 程式,而是逐步具備監控狀態管理、安全回應控制與可靠性驗證的 EDR 原型。
不過,還有一些限制尚未解決,例如短暫程序的漏報、完整系統目錄的監控覆蓋、輪詢效能負載,以及事件式遙測機制。
Day28 讓我對資安監控系統有了不同的理解。
以前我認為,只要寫出足夠多的 Detection Rules,就能讓系統越來越安全。
但今天才真正發現,偵測規則只是整個 EDR 系統的其中一部分。
如果資料收集不完整,規則就沒有辦法判斷;如果去重設計不良,重要事件可能被忽略;如果 Response 沒有安全控制,也可能因為誤報而影響正常系統。
最讓我印象深刻的是第一次 Benchmark 得到全部 100% 的結果。
一開始我以為這代表 BehaviorGuard 非常可靠,但後來才發現是測試方式有問題。修正後,才真正看見短暫程序的漏報現象。
這也讓我學到,資安工程師不能只看見 PASS 就認為系統安全,而是要確認測試方法是否正確、結果是否合理,以及還有哪些沒有被測試到的情況。
Day28 最後,我們雖然沒有徹底解決 Polling 的限制,卻已經能用實際數據說明問題、提出改善方式並驗證改善效果。
我認為這比單純增加幾條偵測規則更有意義。