在 Day21,我們已經將 Detection Engine 與 Response Engine 串接起來,讓 BehaviorGuard 偵測到可疑程序後,可以根據 Alert 的 Severity 產生對應的回應建議。
不過,當時的 Response Engine 仍然使用 Dry Run 模式。
例如,當系統收到 HIGH Alert 時,只會顯示:
Action : Recommend process termination
Mode : DRY RUN
Executed : False
也就是說,系統雖然會建議終止程序,但沒有真正對 Linux 執行任何終止動作。
因此,Day22 我們要開始實作真正的 Process Termination(程序終止)。
今天的目標並不是自動終止所有可疑程序,而是先在 Kali Linux 中建立一個受控測試程序,讓 Response Engine 使用 SIGTERM 發送終止訊號,並確認程序是否成功結束。
在 Linux 系統中,每個正在執行的程序(Process)都有一個 PID(Process ID),也就是程序識別碼。
例如,我們執行:
sleep 60
Linux 就會啟動一個 sleep 程序,讓它等待 60 秒後結束。
如果我們想提前結束這個程序,可以向它發送 Signal(訊號)。
Signal 是 Linux 用來通知程序發生特定事件的一種機制。
其中常見的兩種終止訊號是:
| Signal | 編號 | 說明 |
|---|---|---|
| SIGTERM | 15 | 要求程序結束,程序可以處理或忽略 |
| SIGKILL | 9 | 強制終止程序,無法被程序捕捉或忽略 |
這兩種訊號最大的差別在於,SIGTERM 允許程序在收到訊號後進行清理,例如關閉檔案或釋放資源。
SIGKILL 則是由核心強制終止程序,不會讓程序執行自己的訊號處理程式。
因此,今天我們先選擇 SIGTERM,而不是直接使用 SIGKILL。
不過,SIGTERM 並不保證程序一定會結束,所以發送訊號後還需要確認結果。
今天我們的測試流程如下:
建立 sleep 60 測試程序
↓
取得測試程序 PID
↓
呼叫 terminate_test_process()
↓
檢查授權參數
↓
確認程序物件與允許的測試指令
↓
傳送 SIGTERM
↓
等待程序結束
↓
確認處置結果
與 Day21 不同的是,這次真的會對 Linux 的測試程序發送終止訊號。
但目前尚未接入真正的 Detection-to-Termination 自動處置流程。
首先進入專案:
cd ~/behaviorguard
備份原本的 Response Engine:
cp response/engine.py response/engine_day21_backup.py
接著編輯:
nano response/engine.py
我們保留原本的 handle_alert(),並在檔案下方新增一個函式:
import subprocess
def terminate_test_process(proc, authorized=False):
print("\n========== BehaviorGuard Termination ==========")
if not authorized:
print("[DENIED] Authorization required.")
return False
if not isinstance(proc, subprocess.Popen):
print("[DENIED] Invalid process object.")
return False
if proc.args != ["sleep", "60"]:
print("[DENIED] Not an allowed test process.")
return False
if proc.poll() is not None:
print("[INFO] Process already exited.")
return False
print(f"PID : {proc.pid}")
print("Action : Process Termination")
print("Mode : CONTROLLED TEST")
try:
proc.terminate()
proc.wait(timeout=3)
print("Executed : True")
print("Result : Process exited")
print("==============================================")
return True
except subprocess.TimeoutExpired:
print("[FAIL] Process did not exit in time.")
return False
except OSError as e:
print(f"[ERROR] {e}")
return False
subprocess 是 Python 標準函式庫,讓 Python 程式能夠建立及管理其他作業系統程序。
例如:
proc = subprocess.Popen(["sleep", "60"])
這行程式會在 Linux 啟動一個新的 sleep 60 子程序。
Popen() 會回傳一個程序物件,讓我們可以取得 PID、檢查程序狀態,或對該程序發送終止訊號。
if not authorized:
print("[DENIED] Authorization required.")
return False
這裡要求呼叫函式時,必須明確傳入 authorized=True,否則拒絕執行。
不過,這只是我們在程式中設計的基本控制條件,不是真正的使用者身分驗證或企業級授權機制。
if not isinstance(proc, subprocess.Popen):
return False
這行會確認傳入的是 Python 的 Popen 程序物件,而不是任意數字或其他資料型態。
接著再檢查:
if proc.args != ["sleep", "60"]:
return False
這次只允許指定的 sleep 60 測試指令。
需要注意,這些檢查是受控測試的初步限制,還不是完整的程序身分驗證。
if proc.poll() is not None:
return False
poll() 用來檢查子程序是否已經結束。
如果回傳 None,代表程序當下還沒有結束。
如果回傳整數,則代表程序已經結束,該數字是程序的結束狀態。
因此,在發送 SIGTERM 前,我們先確認程序仍在執行。
今天最重要的兩行是:
proc.terminate()
proc.wait(timeout=3)
在 Linux 上,proc.terminate() 會向指定的子程序發送 SIGTERM。
但發送訊號並不代表程序已經結束。
因此,我們接著使用 proc.wait(timeout=3),最多等待三秒,確認子程序退出並取得其結束狀態。
如果三秒內沒有結束,Python 就會產生 subprocess.TimeoutExpired 例外。
這樣就能區分「已經發送終止訊號」與「已確認程序結束」。
不過,wait() 確認的是程序已退出,並不能單靠它證明程序一定是因 SIGTERM 而結束。
完成 Response Engine 後,我們另外建立:
nano test_day22.py
程式如下:
import subprocess
from response.engine import terminate_test_process
print("========== Day22 Response Test ==========")
# 產生自己的測試程序
proc = subprocess.Popen(["sleep", "60"])
print(f"[TEST] Created process PID: {proc.pid}")
try:
result = terminate_test_process(
proc,
authorized=True
)
if result and proc.poll() is not None:
print("[PASS] Test process terminated successfully.")
else:
print("[FAIL] Termination was not confirmed.")
finally:
if proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=3)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
print("[INFO] Day22 test completed.")
這次測試主要分成三個部分。
第一部分:建立真實子程序
proc = subprocess.Popen(["sleep", "60"])
讓 Kali Linux 建立一個等待 60 秒的測試程序。
第二部分:呼叫 Response Engine
result = terminate_test_process(
proc,
authorized=True
)
這次傳入真正建立的程序物件,並設定測試授權參數。
第三部分:確認與清理
if result and proc.poll() is not None:
print("[PASS] Test process terminated successfully.")
這裡同時確認終止函式回傳成功,以及子程序確實已退出。
另外,我們使用 finally 確保即使測試失敗,也會嘗試清理測試程序,避免留下不必要的背景程序。
finally 中的清理動作屬於測試程式的工作,不是 Response Engine 的自動處置機制。
完成程式後,執行:
PYTHONPATH=. python3 test_day22.py
這次在 Kali Linux 實際取得的結果:
========== Day22 Response Test ==========
[TEST] Created process PID: 2538718
========== BehaviorGuard Termination ==========
PID : 2538718
Action : Process Termination
Mode : CONTROLLED TEST
Executed : True
Result : Process exited
==============================================
[PASS] Test process terminated successfully.
[INFO] Day22 test completed.
從結果可以看到:
2538718 的測試程序。terminate() 發送 SIGTERM。wait() 成功等待到測試程序結束。[PASS],確認本次受控程序終止測試成功。這次與 Day21 的最大差別是:
Day21:
Mode : DRY RUN
Executed : False
Day22:
Mode : CONTROLLED TEST
Executed : True
Result : Process exited
因此,這次不再只是印出終止程序的建議,而是實際透過 Python 對 Linux 子程序執行終止操作。
雖然今天成功終止測試程序,但目前的功能還存在幾個限制。
第一:尚未自動終止偵測到的可疑程序。
今天使用的是 test_day22.py 建立的 sleep 60,不是由 Detection Engine 發現惡意程序後自動觸發終止。
第二:授權機制仍然很簡單。
目前的 authorized=True 只是函式參數,還沒有加入真正的身分驗證、白名單與核准流程。
第三:尚未完成完整的程序身分確認。
雖然我們檢查了 Popen 物件和測試指令,但還沒有完整驗證執行檔路徑、程序建立時間等資訊。
第四:尚未建立 Response Audit Log。
目前終止結果只顯示在 Terminal,還沒有另外保存每次處置的時間、對象、動作及結果。
因此,今天完成的是受控測試程序終止功能,還不是能安全地自動處置任意可疑程序的 EDR Response 系統。
Day22 是我們第一次真正讓 BehaviorGuard 對 Linux 程序執行終止操作。
在前面的實作中,Response Engine 主要依照 Severity 產生建議,實際上並沒有改變系統狀態。
今天透過 Python 的 subprocess 模組,建立了一個可以管理的測試子程序,並利用 SIGTERM 要求它結束,再使用 wait() 確認執行結果。
這次也讓我更清楚了解,程序終止不是單純傳送一個訊號就結束,還需要考慮程序是否仍然存在、能否正常退出,以及測試失敗時如何清理。
另外,我認為真正的 Response 功能必須比 Detection 更謹慎。
因為偵測規則可能產生誤判,如果沒有任何限制就自動終止程序,可能影響正常的系統服務。
目前我們只允許受控的 sleep 60 測試程序進入終止流程,因此還需要進一步改善安全性。
下一篇 Day23,我們預計開始建立 Response Policy(回應安全策略),加入更完整的程序檢查與限制條件,為之後將 Detection 與真正的 Process Termination 串接做準備。