WMI 提供系統管理員強大的控制能力,但也可能被惡意程式利用。理解 WMI 的運作機制,並學習如何監控相關事件,對於系統安全至關重要。本文提供的 Python 程式碼範例,可以幫助讀者更好地理解 WMI 的應用和潛在風險,並學習如何利用 Python 進行 WMI 事件監控和分析。透過分析 WMI 事件日誌,安全人員可以識別惡意程式碼執行,並採取相應的防禦措施。此外,文章也探討了惡意程式碼利用排程任務的技巧,並提供防禦策略,以降低系統遭受攻擊的風險。

使用WMI實作程式碼執行與檢測防禦

在探討Windows Management Instrumentation(WMI)的使用時,我們發現它可以用於實作程式碼執行。WMI是一種強大的管理技術,允許系統管理員監控和控制Windows系統上的各種程式和事件。

利用WMI建立程式

在命令提示字元中,輸入wmic process call create "notepad.exe"將啟動一個記事本例項。雖然我們可以直接使用os.systemsubprocess來執行此命令,但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)

內容解密:

  1. WMIProcessCreation函式:使用wmi函式庫建立一個新的程式。該函式呼叫Win32_Process.Create方法來啟動指定的命令,並傳回新程式的PID。
  2. PSProcessCreation函式:透過呼叫PowerShell來建立一個新的程式。使用subprocess.run執行PowerShell命令,該命令利用WMI建立程式並傳回其PID。
  3. 命令執行:兩種方法都用於啟動記事本,但可以替換為任何有效的終端命令。

防禦者如何監控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)

內容解密:

  1. GetEventLogs函式:查詢WMI活動日誌,篩選出事件ID為23的記錄,這些記錄對應於程式建立事件。
  2. ParseEvents函式:解析從日誌中檢索到的事件,提取執行的命令和建立的程式PID,並將其輸出到控制檯。

啟用WMI日誌記錄

預設情況下,WMI日誌未啟用。要啟用它,需要在事件檢視器中進行以下步驟:

  1. 開啟事件檢視器。
  2. 在檢視選單中,點選“顯示分析和偵錯日誌”。
  3. 瀏覽到“應用程式和服務日誌” > “Microsoft” > “Windows” > “WMI活動”。
  4. 右鍵點選“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資料

雖然EvtQueryEvtNext函式可以簡化查詢和處理事件資料,但它們並未提供易於閱讀的格式。要存取日誌條目中的資料,我們需要將其轉換為XML並提取相關欄位。

將日誌條目轉換為XML

使用win32evtlog中的EvtRender函式將日誌條目轉換為XML。然後,使用xml.etree.ElementTree.fromstring函式將產生的XML字串轉換為可用的格式。

解析XML資料

圖3.3顯示了一個XML物件的範例。如圖所示,XML的結構與圖3.2所示的範例相同。根節點Event有兩個子節點:SystemUserData。在UserData節點內是ProcessCreate,其中包含我們需要的資料:CommandlineCreatedProcessId

使用XML查詢存取特定資料

雖然ElementTree允許我們將XML字串轉換為可用的格式,但它不允許按節點標籤查詢。我們需要透過XML查詢使用根節點的findall()方法來存取CommandlineCreatedProcessId

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}")

程式碼解析:

  1. 匯入必要的模組:匯入了win32evtlog用於操作Windows事件日誌,以及xml.etree.ElementTree用於解析XML。
  2. 定義查詢引數:指定了要查詢的日誌路徑、搜尋方向、查詢條件和會話。
  3. 執行查詢:使用EvtQuery函式執行查詢,取得查詢控制程式碼。
  4. 遍歷查詢結果:使用EvtNext函式分批取得事件,並遍歷這些事件。
  5. 將事件轉換為XML並解析:對每個事件,使用EvtRender將其轉換為XML格式,然後使用ElementTree.fromstring解析XML,最後提取需要的資訊。
  6. 列印結果:列印預出執行的命令和建立的程式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)
# 繼續執行排程任務相關程式碼...

程式碼解析:

  1. 檢查並刪除現有的排程任務:檢查名為"SecurityScan"的任務是否存在,如果存在,則刪除。
  2. 模擬惡意行為:列印預出正在進行惡意活動的訊息。
  3. 計算下一次執行的時間:隨機計算一個時間間隔,並據此設定下一次執行的時間。

排程任務的惡意利用與防禦策略

惡意程式利用排程任務的實作分析

在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))

內容解密:

  1. datetime.now():取得目前的時間。
  2. **timedelta(minutes=1+(random.random()*(1-1))):計算一個隨機的時間間隔,這裡固定為1分鐘。
  3. **os.system('schtasks /create ...'):使用schtasks指令建立一個名為SecurityScan的排程任務,該任務會在計算出的時間執行指定的惡意程式。

檢查與刪除現有的排程任務

在建立新的排程任務之前,TaskScheduler.py會先檢查是否已經存在同名的任務。如果存在,則會將其刪除:

os.system('schtasks /query /tn SecurityScan')
os.system('schtasks /delete /f /tn SecurityScan')

內容解密:

  1. schtasks /query /tn SecurityScan:查詢名為SecurityScan的排程任務是否存在。
  2. 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

內容解密:

  1. allowlist:定義了一個允許清單,包含可信的軟體廠商名稱。
  2. extensions:定義了可執行的檔案副檔名。
  3. CheckValidTask:檢查任務的建立者和任務命令是否可信。如果任務命令指向系統目錄下的可執行檔案,則視為可信;否則,檢查建立者是否在允許清單中。