返回文章列表

WMI程式碼執行與檢測防禦策略

本文探討如何使用 WMI 進行程式碼執行,並提供 Python 程式碼範例示範如何利用 WMI 建立程式和 PowerShell。同時也介紹如何監控 WMI 事件日誌以檢測惡意活動,並提供 Python 程式碼範例解析事件日誌 XML 資料,提取關鍵資訊,例如執行的命令和程式

資安 系統管理

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:檢查任務的建立者和任務命令是否可信。如果任務命令指向系統目錄下的可執行檔案,則視為可信;否則,檢查建立者是否在允許清單中。