WMI 提供系統管理員強大的控制能力,但也可能被惡意程式利用。理解 WMI 的運作機制,並學習如何監控相關事件,對於系統安全至關重要。本文提供的 Python 程式碼範例,可以幫助讀者更好地理解 WMI 的應用和潛在風險,並學習如何利用 Python 進行 WMI 事件監控和分析。透過分析 WMI 事件日誌,安全人員可以識別惡意程式碼執行,並採取相應的防禦措施。此外,文章也探討了惡意程式碼利用排程任務的技巧,並提供防禦策略,以降低系統遭受攻擊的風險。
使用WMI實作程式碼執行與檢測防禦
在探討Windows Management Instrumentation(WMI)的使用時,我們發現它可以用於實作程式碼執行。WMI是一種強大的管理技術,允許系統管理員監控和控制Windows系統上的各種程式和事件。
利用WMI建立程式
在命令提示字元中,輸入wmic process call create "notepad.exe"將啟動一個記事本例項。雖然我們可以直接使用os.system或subprocess來執行此命令,但Python還提供了wmi函式庫,該函式庫公開了此功能。
WMIExecution.py 程式碼範例
import wmi
import subprocess
def WMIProcessCreation(command):
c = wmi.WMI()
process = c.Win32_Process.Create(CommandLine=command)
print("Process %s created with PID %s" % (command, process[0].ProcessId))
def PSProcessCreation(command):
ps_command = f"powershell invoke-wmimethod win32_process -name create -argumentlist '{command}' | select ProcessId | %{{$_.ProcessId}}"
p = subprocess.run(ps_command, shell=True, stdout=subprocess.PIPE)
print("Process %s created with PowerShell, PID %s" % (command, p.stdout.decode("utf-8")))
command = "notepad.exe"
WMIProcessCreation(command)
PSProcessCreation(command)
內容解密:
- WMIProcessCreation函式:使用
wmi函式庫建立一個新的程式。該函式呼叫Win32_Process.Create方法來啟動指定的命令,並傳回新程式的PID。 - PSProcessCreation函式:透過呼叫PowerShell來建立一個新的程式。使用
subprocess.run執行PowerShell命令,該命令利用WMI建立程式並傳回其PID。 - 命令執行:兩種方法都用於啟動記事本,但可以替換為任何有效的終端命令。
防禦者如何監控WMI事件
為了防禦WMI被用於惡意程式碼執行,監控WMI事件至關重要。
WMIDetection.py 程式碼範例
import win32evtlog
import xml.etree.ElementTree as ET
server = "localhost"
logtype = "Microsoft-Windows-WMI-Activity/Trace"
flags = win32evtlog.EvtQueryForwardDirection
query = "*[System[EventID=23]]"
def GetEventLogs():
q = win32evtlog.EvtQuery(logtype, flags, query)
events = ()
while True:
e = win32evtlog.EvtNext(q, 100, -1, 0)
if e:
events = events + e
else:
break
return events
def ParseEvents(events):
for event in events:
xml = win32evtlog.EvtRender(event, 1)
root = ET.fromstring(xml)
path = './{*}UserData/{*}ProcessCreate/{*}'
name = root.findall(path+'Commandline')[0].text
pid = root.findall(path+'CreatedProcessId')[0].text
print("Process %s launched with PID %s" % (name, pid))
events = GetEventLogs()
ParseEvents(events)
內容解密:
- GetEventLogs函式:查詢WMI活動日誌,篩選出事件ID為23的記錄,這些記錄對應於程式建立事件。
- ParseEvents函式:解析從日誌中檢索到的事件,提取執行的命令和建立的程式PID,並將其輸出到控制檯。
啟用WMI日誌記錄
預設情況下,WMI日誌未啟用。要啟用它,需要在事件檢視器中進行以下步驟:
- 開啟事件檢視器。
- 在檢視選單中,點選“顯示分析和偵錯日誌”。
- 瀏覽到“應用程式和服務日誌” > “Microsoft” > “Windows” > “WMI活動”。
- 右鍵點選“Trace”並選擇“啟用日誌”。
啟用WMI日誌後,執行相關程式碼將生成包含程式建立事件的日誌條目,這些資訊可用於進一步調查和事件回應。
使用Python存取WMI事件日誌
在Event Viewer中檢視範例日誌後,下一步是在Python中存取相同的資料。為此,我們將使用win32evtlog函式庫,但將以不同的方式存取記錄。因為「應用程式和服務日誌」無法被之前使用的函式存取。
使用EvtQuery函式查詢事件日誌
本例中,我們使用EvtQuery函式來請求一組事件日誌。該函式接受四個引數:
- Path:日誌檔案的路徑。對於WMI事件,這是
Microsoft-Windows-WMI-Activity/Trace。 - Flags:指定搜尋日誌條目的方式。我們使用
EvtQueryForwardDirection從最舊到最新進行順序搜尋。 - Query:定義要存取的日誌,根據圖3.2所示的日誌條目結構。在此例中,我們需要事件ID為23的日誌條目。使用查詢陳述式
*[System[EventID=23]]。 - Session:可以指向遠端機器,或設為
None表示本機。
執行查詢會產生一組結果,但我們需要呼叫EvtNext來實際存取這些結果。該函式接受查詢物件、要存取的結果數量、逾時值(-1表示無逾時)和一個必須為0的旗標作為輸入。
處理事件日誌XML資料
雖然EvtQuery和EvtNext函式可以簡化查詢和處理事件資料,但它們並未提供易於閱讀的格式。要存取日誌條目中的資料,我們需要將其轉換為XML並提取相關欄位。
將日誌條目轉換為XML
使用win32evtlog中的EvtRender函式將日誌條目轉換為XML。然後,使用xml.etree.ElementTree.fromstring函式將產生的XML字串轉換為可用的格式。
解析XML資料
圖3.3顯示了一個XML物件的範例。如圖所示,XML的結構與圖3.2所示的範例相同。根節點Event有兩個子節點:System和UserData。在UserData節點內是ProcessCreate,其中包含我們需要的資料:Commandline和CreatedProcessId。
使用XML查詢存取特定資料
雖然ElementTree允許我們將XML字串轉換為可用的格式,但它不允許按節點標籤查詢。我們需要透過XML查詢使用根節點的findall()方法來存取Commandline和CreatedProcessId。
import win32evtlog
import xml.etree.ElementTree as ET
# 定義查詢引數
path = 'Microsoft-Windows-WMI-Activity/Trace'
flags = win32evtlog.EvtQueryForwardDirection
query = '*[System[EventID=23]]'
session = None
# 執行查詢
query_handle = win32evtlog.EvtQuery(path, flags, query, session)
# 存取查詢結果
while True:
events = win32evtlog.EvtNext(query_handle, 100, -1, 0)
if len(events) == 0:
break
for event in events:
# 將事件轉換為XML
xml = win32evtlog.EvtRender(event, win32evtlog.EvtRenderEventXml)
root = ET.fromstring(xml)
# 解析XML以取得Commandline和CreatedProcessId
command_line_nodes = root.findall('./{*}UserData/{*}ProcessCreate/{*}Commandline')
process_id_nodes = root.findall('./{*}UserData/{*}ProcessCreate/{*}CreatedProcessId')
if command_line_nodes and process_id_nodes:
command_line = command_line_nodes[0].text
process_id = process_id_nodes[0].text
print(f"執行的命令:{command_line},PID:{process_id}")
程式碼解析:
- 匯入必要的模組:匯入了
win32evtlog用於操作Windows事件日誌,以及xml.etree.ElementTree用於解析XML。 - 定義查詢引數:指定了要查詢的日誌路徑、搜尋方向、查詢條件和會話。
- 執行查詢:使用
EvtQuery函式執行查詢,取得查詢控制程式碼。 - 遍歷查詢結果:使用
EvtNext函式分批取得事件,並遍歷這些事件。 - 將事件轉換為XML並解析:對每個事件,使用
EvtRender將其轉換為XML格式,然後使用ElementTree.fromstring解析XML,最後提取需要的資訊。 - 列印結果:列印預出執行的命令和建立的程式ID。
執行WMIDetection.py的結果
執行WMIDetection.py後,它正確地識別了使用WMI Python函式庫建立的程式及其PID,也檢測到了使用PowerShell建立的程式及其PID。
排程惡意任務
攻擊者在獲得目標系統的存取權後,可能無法直接執行惡意程式碼。透過利用任務排程功能,攻擊者不僅可以實作程式碼執行,還可以透過分散攻擊鏈來使法醫分析更加複雜。
TaskScheduler.py範例
import os, random
from datetime import datetime, timedelta
if os.system("schtasks /query /tn SecurityScan") == 0:
os.system("schtasks /delete /f /tn SecurityScan")
print("I am doing malicious things")
filedir = os.path.join(os.getcwd(), "TaskScheduler.py")
maxInterval = 1
interval = 1 + (random.random() * (maxInterval - 1))
dt = datetime.now() + timedelta(minutes=interval)
# 繼續執行排程任務相關程式碼...
程式碼解析:
- 檢查並刪除現有的排程任務:檢查名為"SecurityScan"的任務是否存在,如果存在,則刪除。
- 模擬惡意行為:列印預出正在進行惡意活動的訊息。
- 計算下一次執行的時間:隨機計算一個時間間隔,並據此設定下一次執行的時間。
排程任務的惡意利用與防禦策略
惡意程式利用排程任務的實作分析
在Windows系統中,惡意程式可以透過schtasks指令來建立排程任務,以實作程式碼的自動執行。以下是一個名為TaskScheduler.py的惡意程式範例,展示瞭如何利用排程任務來執行惡意程式碼:
import os
import random
from datetime import datetime, timedelta
# 計算下一次執行的時間和日期
dt = datetime.now() + timedelta(minutes=1+(random.random()*(1-1)))
t = "%s:%s" % (str(dt.hour).zfill(2), str(dt.minute).zfill(2))
d = "%s/%s/%s" % (str(dt.month).zfill(2), str(dt.day).zfill(2), dt.year)
# 建立排程任務
filedir = "惡意程式路徑"
os.system('schtasks /create /tn SecurityScan /tr \"%s\" /sc once /st %s /sd %s' % (filedir, t, d))
內容解密:
datetime.now():取得目前的時間。- **
timedelta(minutes=1+(random.random()*(1-1))):計算一個隨機的時間間隔,這裡固定為1分鐘。 - **
os.system('schtasks /create ...'):使用schtasks指令建立一個名為SecurityScan的排程任務,該任務會在計算出的時間執行指定的惡意程式。
檢查與刪除現有的排程任務
在建立新的排程任務之前,TaskScheduler.py會先檢查是否已經存在同名的任務。如果存在,則會將其刪除:
os.system('schtasks /query /tn SecurityScan')
os.system('schtasks /delete /f /tn SecurityScan')
內容解密:
schtasks /query /tn SecurityScan:查詢名為SecurityScan的排程任務是否存在。schtasks /delete /f /tn SecurityScan:強制刪除名為SecurityScan的排程任務。
防禦策略:監控排程任務
為了防禦惡意程式利用排程任務,系統管理員可以監控系統中的排程任務。以下是一個名為ScheduleTracker.py的範例指令碼,用於檢查排程任務的可信度:
import os
import pathlib
import subprocess
def CheckValidTask(creator, task):
allowlist = ["Microsoft", "Mozilla", "Adobe Systems Incorporated"]
extensions = [".exe", ".py", ".dll"]
trusted = [creator for x in allowlist if creator.startswith(x)]
executable = [task for ext in extensions if ext in task]
if executable:
exe = task.split(" ")[0]
p = os.path.expandvars(exe).lower()
if p.startswith(r"c:\\windows\\system32") or p.startswith(r"c:\windows\system32"):
return True
else:
return trusted
內容解密:
allowlist:定義了一個允許清單,包含可信的軟體廠商名稱。extensions:定義了可執行的檔案副檔名。CheckValidTask:檢查任務的建立者和任務命令是否可信。如果任務命令指向系統目錄下的可執行檔案,則視為可信;否則,檢查建立者是否在允許清單中。