昨天 Day14,我替 BehaviorGuard 加入了 Correlation Engine。
原本獨立的 Alert:
BG-CMD-002
Python Inline Code Execution
BG-NET-005
Shell or Interpreter Network Activity
BG-NET-003
Unexpected External Connection
已經可以透過:
Time Window
+
Same PID
+
Rule Sequence
+
Duplicate Suppression
串成一條 Attack Chain。
例如:
BG-CMD-002
↓
BG-NET-005
↓
BG-NET-003
↓
BG-CORR-005
CRITICAL
但是昨天的版本其實還存在一個非常大的限制:
三個 Alert 必須來自同一個 PID。
也就是:
BG-CMD-002 PID 7000
↓
BG-NET-005 PID 7000
↓
BG-NET-003 PID 7000
才能被 BehaviorGuard 關聯。
但真實 Linux 系統裡,攻擊行為通常不會永遠待在同一個 Process。
因此 Day15 要解決的問題就是:
讓 BehaviorGuard 不只知道:
「這是不是同一個 PID?」
還能進一步知道:
「雖然 PID 不同,但這些 Process 是不是其實有 Parent / Child 關係?」
Linux 裡,每一個 Process 都有自己的:
PID
Process ID
例如:
python3
PID = 2306118
PID 可以理解成這個 Process 的「編號」。
但是 Process 通常不是憑空產生的。
很多 Process 都是由另外一個 Process 建立。
因此 Linux 還有:
PPID
Parent Process ID
也就是:
建立我的 Process 是誰?
例如今天實際測試時,BehaviorGuard 抓到:
PID : 2306118
PPID : 2306074
Process : python3
Parent : bash
Ancestors : [2306074, 2306000, 2305992, 1]
這代表:
python3
PID 2306118
它的 Parent:
bash
PID 2306074
因此:
python3 的 PPID
=
bash 的 PID
也就是:
bash
PID 2306074
↓
python3
PID 2306118
PPID 2306074
這就是最基本的 Parent / Child Process 關係。
如果 Child Process 又建立新的 Process:
bash
↓
python3
↓
sleep
每一層都有自己的 PID。
例如:
bash
PID 7000
↓
python3
PID 7010
PPID 7000
↓
nc
PID 7020
PPID 7010
這就形成一棵:
也可以稱為:
Process Lineage
Lineage 可以理解成「血緣關係」。
如果從 PID 7020 往上找:
7020
↑
7010
↑
7000
那:
7010
7000
就是 PID 7020 的 Ancestors。
因此可以表示成:
ancestors = [7010, 7000]
只看單一 Process 時:
python3
不一定可疑。
只看到:
bash
也不一定可疑。
只看到:
nc
也不能直接說它一定是攻擊。
但是如果看到:
apache2
↓
bash
↓
python3
↓
nc
事情就完全不一樣了。
因為 Process Tree 提供了一個很重要的資訊:
也就是:
「這個 Process 到底是誰啟動的?」
例如:
使用者 Terminal
↓
bash
很正常。
但:
Web Server
↓
bash
可能就比較值得注意。
再例如:
bash
↓
python3
↓
network connection
如果這些行為又同時命中多條 Detection Rule,就更有可能屬於同一條 Attack Chain。
所以 EDR 不應該只看:
Process Name
而是要一起看:
PID
PPID
Parent
Ancestor
Command
Network
File
這也是 Day15 要補上的資訊。
Day14 的 Correlation Engine 原本有:
alerts_by_pid = {}
收到 Alert 後:
pid = alert.get("pid")
if pid not in alerts_by_pid:
alerts_by_pid[pid] = []
alerts_by_pid[pid].append(alert)
這個設計很容易理解:
PID 7000
→ 放進 7000 群組
PID 7010
→ 放進 7010 群組
PID 7020
→ 放進 7020 群組
問題就在這裡。
假設真正的 Process Tree 是:
PID 7000
↓
PID 7010
↓
PID 7020
它們明明是一家人。
但是 Day14 的程式會變成:
7000 一組
7010 一組
7020 一組
Correlation Engine 根本沒有機會把它們放在一起分析。
所以今天第一個改變就是:
不再把「PID 是否相同」當成唯一的 Process Relationship。
我的 process_monitor.py 本來就已經會從:
/proc/<pid>/status
取得:
PID
PPID
因此不用重新設計整個 Process Monitor。
我加入了一個:
get_process_lineage()
概念如下:
def get_process_lineage(process, process_map, max_depth=4):
ancestors = []
current_ppid = process.get("ppid")
depth = 0
while current_ppid and depth < max_depth:
parent = process_map.get(current_ppid)
if parent is None:
break
ancestors.append(parent["pid"])
current_ppid = parent.get("ppid")
depth += 1
return ancestors
假設現在 Process Tree 是:
bash
PID 7000
↓
python3
PID 7010
↓
nc
PID 7020
現在要分析:
PID 7020
首先:
7020 的 PPID = 7010
所以:
ancestors = [7010]
再找:
7010 的 PPID = 7000
變成:
ancestors = [7010, 7000]
因此最後:
get_process_lineage(7020)
概念上會得到:
[7010, 7000]
這就代表:
7020
↑
7010
↑
7000
我的 function 裡還有:
max_depth=4
也就是最多往上找四層。
這個限制很重要。
如果完全沒有限制,一個 Process 可以一直:
Child
↑
Parent
↑
Grandparent
↑
...
↑
systemd
PID 1
最後很多 Process 都可能一路追到:
PID 1
如果把太遠的 Process Relationship 全部視為強關係,很容易造成:
也就是:
把其實沒什麼關係的 Process 硬串成同一條事件。
所以第一版先限制 Process Lineage 深度,避免關係範圍無限制擴大。
今天實際執行:
PYTHONPATH=. python agent/process_monitor.py
接著產生:
bash
↓
python3
↓
sleep
BehaviorGuard 成功抓到:
[ALERT] BG-CMD-002
Rule : Python Inline Code Execution
Severity : MEDIUM
PID : 2306118
PPID : 2306074
Process : python3
Parent : bash
Ancestors : [2306074, 2306000, 2305992, 1]
Command : python3 -c import subprocess, time; subprocess.Popen(["sleep","20"]); time.sleep(20)
代表 BehaviorGuard 現在不只知道:
這是 python3
還知道:
PID
PPID
Parent
Ancestors
Process Context 已經比之前完整很多。
接著建立:
correlation/process_tree.py
其中最重要的 function 是:
def are_processes_related(alert_a, alert_b):
pid_a = alert_a.get("pid")
pid_b = alert_b.get("pid")
if pid_a is None or pid_b is None:
return False
if pid_a == pid_b:
return True
ancestors_a = alert_a.get("ancestors", [])
ancestors_b = alert_b.get("ancestors", [])
if pid_a in ancestors_b:
return True
if pid_b in ancestors_a:
return True
return False
這個 function 就是在回答:
Alert A 和 Alert B 的 Process 到底有沒有血緣關係?
目前 BehaviorGuard 會認為以下三種情況存在 Process Relationship。
| 情況 | 結果 |
|---|---|
| Same PID | Related |
| A 是 B 的 Ancestor | Related |
| B 是 A 的 Ancestor | Related |
| 只有共同 Ancestor | NOT Related |
第一種最簡單:
A PID 7000
B PID 7000
結果:
True
也就是保留 Day14 原本的 Same PID Correlation。
第二種:
Alert A
PID 7000
另一個:
Alert B
PID 7010
Ancestors [7000]
因為:
7000 in [7000]
所以:
PID 7000
是 PID 7010 的 Ancestor
結果:
True
第三種可以跨更多層。
例如:
Alert A
PID 7000
以及:
Alert B
PID 7020
Ancestors [7010, 7000]
雖然:
7000 != 7020
但是:
7000 in [7010, 7000]
所以依然可以知道:
7000
↓
7010
↓
7020
屬於同一條 Process Lineage。
昨天 Day14:
Same PID
↓
Correlation
今天改成:
Same PID
OR
Parent / Child
OR
Ancestor / Descendant
↓
Correlation
Correlation Engine 不再先用:
alerts_by_pid = {}
把所有不同 PID 分開。
而是改成:
if not are_processes_related(
previous_alert,
candidate
):
continue
意思就是:
下一個 Alert 除了 Rule 順序正確以外,它的 Process 還必須跟上一階段 Process 有 lineage 關係。
Day15 並沒有把昨天的 Correlation 邏輯全部推翻。
昨天建立的:
Time Window
Rule Sequence
Duplicate Suppression
全部保留。
因此現在完整條件變成:
Time Window
+
Rule Sequence
+
Process Relationship
+
Duplicate Suppression
例如:
BG-CMD-002
PID 7000
↓
BG-NET-005
PID 7010
PPID 7000
↓
BG-NET-003
PID 7020
PPID 7010
Rule 順序符合:
BG-CMD-002
↓
BG-NET-005
↓
BG-NET-003
Process Tree 也符合:
7000
↓
7010
↓
7020
因此:
BG-CORR-005
成立。
為了確認不同 PID 真的可以 Correlation,我建立三個測試 Alert。
第一個:
alert1 = {
"rule": "BG-CMD-002",
"name": "Python Inline Code Execution",
"severity": "MEDIUM",
"pid": 7000,
"ppid": 6500,
"ancestors": [
6500
]
}
第二個:
alert2 = {
"rule": "BG-NET-005",
"name": "Shell or Interpreter Network Activity",
"severity": "HIGH",
"pid": 7010,
"ppid": 7000,
"ancestors": [
7000,
6500
]
}
第三個:
alert3 = {
"rule": "BG-NET-003",
"name": "Unexpected External Connection",
"severity": "MEDIUM",
"pid": 7020,
"ppid": 7010,
"ancestors": [
7010,
7000,
6500
]
}
可以畫成:
PID 6500
↓
PID 7000
BG-CMD-002
↓
PID 7010
BG-NET-005
↓
PID 7020
BG-NET-003
這次三個 PID:
7000
7010
7020
完全不同。
執行:
PYTHONPATH=. python test_process_tree_correlation.py
成功出現:
========== BehaviorGuard Correlated Alert ==========
Rule : BG-CORR-005
Name : Suspicious Execution and Network Activity
Severity : CRITICAL
Process Lineage:
PID 7000 PPID 6500 Ancestors [6500]
PID 7010 PPID 7000 Ancestors [7000, 6500]
PID 7020 PPID 7010 Ancestors [7010, 7000, 6500]
Attack Chain:
-> BG-CMD-002 (Python Inline Code Execution) [PID 7000]
-> BG-NET-005 (Shell or Interpreter Network Activity) [PID 7010]
-> BG-NET-003 (Unexpected External Connection) [PID 7020]
=====================================================
這代表:
7000 != 7010 != 7020
但是 BehaviorGuard 已經知道:
7000
↓
7010
↓
7020
是一條 Process Tree。
因此仍然成功產生:
BG-CORR-005
CRITICAL

這次測試還同時出現:
BG-CORR-002
Python Execution with External Connection
Severity: HIGH
它使用:
BG-CMD-002
PID 7000
以及:
BG-NET-003
PID 7020
乍看之下:
7000 != 7020
但是 PID 7020 的 Ancestors:
[7010, 7000, 6500]
包含:
7000
所以系統知道:
PID 7000
↓
...
↓
PID 7020
其實存在祖先與後代關係。
因此:
BG-CORR-002
也可以成立。
這證明 Process Tree Correlation 不只支援直接 Parent / Child,也可以支援限定深度內的 Ancestor / Descendant。
如果只要:
「有共同 Ancestor」
就算 Related,會發生什麼事情?
假設:
PID 5000
/ \
6000 7000
↓ ↓
8000 9000
PID 8000 的 Ancestors:
[6000, 5000]
PID 9000 的 Ancestors:
[7000, 5000]
兩個 Process 都有:
5000
這個共同祖先。
但:
8000
並不是:
9000
的 Parent。
9000 也不是 8000 的 Ancestor。
它們只是:
兩條不同 Branch
而已。
如果只因為:
都有 5000
就把兩個 Alert 串起來,會造成:
Over-Correlation 可以理解成:
系統把其實沒有直接關係的事件關聯在一起。
Linux 裡這個問題特別重要。
因為很多 Process 最後都可能追到:
systemd
PID 1
如果 BehaviorGuard 的規則是:
只要有共同 ancestor 就 Related
那可能發生:
apache2
和:
NetworkManager
因為最後都有:
PID 1
就被錯誤串成同一次攻擊。
這樣 Correlation 反而會產生大量 False Positive。
因此我的 are_processes_related() 特別沒有寫成:
如果 ancestors 有交集:
return True
而是只接受:
Same PID
或:
A 是 B 的 ancestor
或:
B 是 A 的 ancestor
要確認系統真的沒有 Over-Correlation,我另外建立:
test_false_correlation.py
測試兩個 Process:
PID 5000
/ \
6000 7000
↓ ↓
PID 8000 PID 9000
第一個 Alert:
alert1 = {
"rule": "BG-CMD-002",
"name": "Python Inline Code Execution",
"severity": "MEDIUM",
"pid": 8000,
"ppid": 6000,
"ancestors": [
6000,
5000
]
}
第二個 Alert:
alert2 = {
"rule": "BG-NET-003",
"name": "Unexpected External Connection",
"severity": "MEDIUM",
"pid": 9000,
"ppid": 7000,
"ancestors": [
7000,
5000
]
}
兩邊都有:
5000
但是:
8000 不是 9000 的 ancestor
9000 也不是 8000 的 ancestor
所以:
are_processes_related()
應該回傳:
False
執行:
PYTHONPATH=. python test_false_correlation.py
結果:
Sending unrelated Command Alert...
Sending unrelated Network Alert...
If no BG-CORR-002 appears, the negative test passed.
而且畫面中:
沒有出現 BG-CORR-002
代表:
Negative Test ✅
成功。
這裡放今天
test_false_correlation.py的截圖。
畫面中沒有出現 BG-CORR-002,證明兩個 Process 即使擁有共同 Ancestor,也不會直接被視為同一條 Process Lineage。

今天我覺得這兩個測試非常重要。
| 測試 | Process 關係 | 結果 |
|---|---|---|
| Positive Test | 7000 → 7010 → 7020 | Correlation ✅ |
| Negative Test | 8000 與 9000 只有共同 Ancestor | Correlation ❌ |
也就是:
有真正 Parent / Child / Ancestor 關係
↓
應該關聯
但是:
只是剛好有共同 Ancestor
↓
不應該關聯
這讓 Process Tree Correlation 不只是:
「抓得到」
還開始處理另一個資安產品非常重要的問題:
「不要抓錯」
昨天 Day14:
BG-CMD-002
PID 7000
↓
BG-NET-005
PID 7000
↓
BG-NET-003
PID 7000
必須全部:
Same PID
才能 Correlation。
Day15:
BG-CMD-002
PID 7000
↓
BG-NET-005
PID 7010
PPID 7000
↓
BG-NET-003
PID 7020
PPID 7010
即使:
7000 != 7010 != 7020
BehaviorGuard 還是能透過:
Parent
Ancestor
Process Lineage
判斷:
它們其實是同一條 Process Tree。
所以整個 Correlation 能力從:
Same PID Correlation
升級成:
Process Tree Correlation
經過 Day14 和 Day15,目前一條 Attack Chain 要成立,可以同時考慮:
| Context | 用途 |
|---|---|
| Time Window | 確認事件是否發生在接近時間 |
| Rule Sequence | 確認事件發生順序 |
| PID | 判斷是否同一 Process |
| PPID | 找 Parent Process |
| Ancestors | 找 Process Lineage |
| Process Relationship | 判斷 Parent / Child / Ancestor |
| Duplicate Suppression | 避免相同 Alert 一直重複 |
| Correlation Rule | 定義哪些行為組合有安全意義 |
所以現在已經不是:
if suspicious:
alert
這麼簡單。
而是:
Alert
↓
Time Context
↓
Process Context
↓
Behavior Sequence
↓
Correlation
↓
Attack Chain
Day14 很容易誤以為:
PID 不同
=
事件不同
但實際上:
bash PID 7000
↓
python3 PID 7010
↓
nc PID 7020
雖然三個 PID 完全不同,仍然可能屬於同一次行為。
因此 EDR 不能只看:
PID
還必須看:
PPID
Parent
Ancestor
Process Tree
同一個程式,由不同 Parent 啟動,安全意義可能完全不同。
例如:
Terminal
↓
bash
很正常。
但是:
apache2
↓
bash
就可能值得調查。
所以:
Who spawned this process?
是 Endpoint Detection 很重要的問題。
只看 PPID:
7020
↓
7010
只能知道一層。
加入 Ancestor:
7020
↓
7010
↓
7000
↓
6500
就能知道更完整的 Process Lineage。
這對多階段攻擊特別重要。
如果規則太嚴:
只能 Same PID
可能漏掉真正的攻擊。
這比較接近:
False Negative
但是如果規則太寬:
只要共同 Ancestor 就算 Related
又會把很多正常行為串在一起。
這會增加:
False Positive
因此 Detection Engineering 很重要的一件事情就是:
找到中間的平衡。
以前做專題時,我比較常測:
「功能有沒有成功觸發?」
今天開始發現還有另一種測試同樣重要:
「不應該觸發的情況,有沒有真的不觸發?」
也就是:
Positive Test:
有攻擊鏈
↓
有 Alert
Negative Test:
沒有真正關係
↓
不能 Alert
兩個都通過,才能比較有信心說規則設計是合理的。
雖然 Process Tree Correlation 已經成功,但目前仍然不是完整 EDR。
目前主要還有幾個限制。
第一個:
Process Lineage 最大深度目前有限制
目前:
max_depth = 4
這是為了避免 Over-Correlation,但是也可能造成太深的 Process Tree 無法完整追蹤。
第二個:
目前測試 Correlation 時使用的 Alert,仍然有部分是:
test_process_tree_correlation.py
手動建立的測試資料。
真正理想的架構應該是:
Process Monitor
↓
Detection Engine
↓
Real Alert
↓
加入 PID / PPID / Ancestors
↓
Correlation Engine
也就是讓真實 Detection Pipeline 自動送進 Correlation,而不是靠測試檔建立 Alert。
這會是後續需要整合的方向。
第三個:
Process Tree 只解決:
Process ↔ Process
的關係。
但我們未來還要處理:
Process
↓
Network
↓
File
↓
Persistence
這些不同 Telemetry 之間的關係。
也就是之後的:
| 功能 | 狀態 |
|---|---|
| PID 蒐集 | ✅ |
| PPID 蒐集 | ✅ |
| Parent Process | ✅ |
| Ancestor List | ✅ |
| Process Lineage | ✅ |
| Process Tree Helper | ✅ |
| Same PID Correlation 保留 | ✅ |
| Parent / Child Correlation | ✅ |
| Ancestor / Descendant Correlation | ✅ |
| Different PID Correlation | ✅ |
| Rule Sequence 保留 | ✅ |
| Time Window 保留 | ✅ |
| Duplicate Suppression 保留 | ✅ |
| BG-CORR-005 跨 PID Attack Chain | ✅ |
| Positive Test | ✅ |
| Negative Test | ✅ |
| Over-Correlation Prevention | ✅ |
Day14 的 BehaviorGuard 已經可以把多個 Alert 串成 Attack Chain。
但是當時最大的限制是:
所有 Alert 必須使用同一個 PID。
Day15 加入 Process Tree 之後,BehaviorGuard 開始可以理解:
bash
PID 7000
↓
python3
PID 7010
↓
nc
PID 7020
雖然:
PID 不同
但是透過:
PPID
Parent
Ancestor
Process Lineage
仍然可以判斷它們屬於同一條 Process Tree。
最後成功完成:
BG-CMD-002
PID 7000
↓
BG-NET-005
PID 7010
↓
BG-NET-003
PID 7020
↓
BG-CORR-005
CRITICAL
另外,我也特別加入 Negative Test。
確認:
兩個 Process 即使擁有共同 Ancestor
只要彼此不存在真正的:
Parent / Child
或
Ancestor / Descendant
關係,就不會被 BehaviorGuard 錯誤關聯。
今天最大的收穫是:
Correlation 不只是把越多 Alert 串在一起越好,而是要找到真正有意義的關係。
BehaviorGuard 從昨天的:
Same PID Correlation
正式進入:
Process Tree Correlation
下一步就可以繼續思考:
Process
+
Command
+
Network
+
File
+
Persistence
要怎麼真正串成一個跨 Telemetry 的 Incident。
也就是讓 BehaviorGuard 從:
「知道哪些 Process 有關」
繼續往:
「還原整段攻擊行為」
前進。