如何還原混淆的 PowerShell 指令碼

還原混淆的 PowerShell 指令碼,就是一層一層地撤銷攻擊者為隱藏指令碼真實行為而做的變換——而且全程絕不執行這個指令碼。一段可疑的單行命令,披著一堵 Base64 的牆,或纏著字元碼和字串拼接而來,底下往往是一段再普通不過的 PowerShell 命令:一次下載、一段解碼後的負載、一段只在記憶體裡執行、從不落碟的命令。混淆不是為了炫技,它是為了繞過特徵掃描、讓命令列一眼看不懂、讓分析人員在看到關鍵部分之前就放棄。本文講清每一層是做什麼的、如何把它剝離成惰性文字、以及你找到的那些片段實際意味著什麼。整個流程可以在PowerShell 還原工具裡走完——逐層解碼,全程在瀏覽器本地完成,不執行任何東西,也不讓任何東西離開你的機器。

攻擊者為什麼混淆 PowerShell——以及為什麼編碼層是首先要剝掉的東西

PowerShell 預裝在每一台 Windows 機器上,能從網際網路下載並執行程式碼而不往磁碟寫檔案,還帶著啟動它的人的權限執行。這種組合使它成為 MITRE ATT&CK 框架裡被濫用最多的工具之一,編號 T1059.001,也使攻擊者拿到一段惡意 PowerShell 命令後做的第一件事就是把它藏起來。一段明文命令,比如 DownloadString 後面跟 Invoke-Expression,偵測起來毫不費力:任何掃描器、任何日誌複查、任何瞥一眼行程命令列的人都會發現它。而經過編碼、壓縮、切碎之後,同一段命令變成了一串匹配不到任何已知特徵、人類讀者也看不懂的字元。

這也正是編碼層必須最先剝掉的原因。在解碼之前,你根本不知道眼前的是安裝程式、惡作劇,還是竊取認證的工具——其餘一切判斷都只是猜測。好消息是,混淆幾乎總能透過靜態還原逆轉:負載必須能被執行它的機器解開,所以每一層都有確定的逆操作。Base64 能解碼、gzip 能解壓縮、字元碼能對映回字母、拼接的字串能接回去。這項工作是機械的,不是神秘的,卻是分流一段可疑指令碼時價值最高的步驟:一次解碼就把「看不懂的亂碼」變成一段可以真正推理的命令。

-EncodedCommand 與 UTF-16LE:看起來像亂碼的 Base64 為什麼通常是可讀文字

你會遇到的最常見的 PowerShell 混淆形式只有一個開關:powershell.exe -EncodedCommand,後面跟一長串 Base64。它看起來像隨機字元,但幾乎總是一段普通的指令碼,只是編碼方式會讓第一次見到的人吃驚。PowerShell 的 -EncodedCommand 要求 Base64 解碼後是 UTF-16LE——每個字元兩個位元組,每個 ASCII 字母後面跟一個零位元組——因為這就是 Windows 上 .NET 字串的編碼。所以無害命令 Write-Host "hello" 的 Base64 以 SQB3AHIAaQB0AGUALQBIAG8AcwB0ACA… 開頭,而反覆出現的字母 "A" 就是那些零位元組留下的痕跡。

知道這一點,你讀編碼字串的方式就變了。一段解碼成 UTF-8 亂碼的 Base64,很可能根本不是 UTF-8:先試 UTF-16LE,「亂碼」就會變成可讀的 PowerShell。沒有 -EncodedCommand 開關的裸 Base64 字串也一樣——解碼後是可列印文字的長串合法 Base64,就是同一個花招,只是沒帶開關。實用的規則很簡單:在 PowerShell 命令列裡看到長串 Base64,就先假定它是一段指令碼,直到解碼證明不是;而且要用一個從不執行它的工具把它當作純文字惰性地解碼,而不是貼到聊天視窗或終端機裡看看會發生什麼。

層層疊加:壓縮、字元碼、拼接、-f、反引號

真實的負載很少只停在一層。-EncodedCommand 解開之後,底下的指令碼往往自己又被編碼了一次:一段 gzip 壓縮後又包了一層 Base64,或者一串像 [char]68+[char]105 這樣的字元碼、一個數字一個數字地拼出命令,或者一段被切成碎片再用加號黏回去的字串,或者一個 -f 格式運算子在執行時把引數填進格式字串,或者關鍵字裡撒了反引號,讓 Do`wnloadString 躲過對 DownloadString 的簡單搜尋。每一層存在的理由各不相同。壓縮把長負載壓成短短的一行,並徹底躲過文字搜尋。字元碼和拼接讓可疑的詞從不以字面形式出現在檔案裡,從而躲過特徵比對。-f 運算子和反引號在保持指令碼語法有效的前提下躲過簡單的字串搜尋。

對付它們的辦法都一樣:按順序一層一層剝,直到沒有哪一層還能剝。解開 Base64,再檢查結果是不是壓縮過的並解壓縮,再解析字元碼、接合碎片、代入格式引數、去掉反引號——每一步產出的都是下一步能處理的純文字。一個把每層都做成可見時間軸卡片的工具——每層一張卡,寫著剝離前與剝離後——比一個只默默輸出最終結果的工具更有價值,因為時間軸本身就是證據:它說明存在過哪些層;而當還原提前停下——「無法繼續還原」——那是一條發現,不是失敗。它告訴你負載裡有靜態分析夠不到的東西:動態的、加密的,或只在執行時才拼出來的內容。

指令碼區塊日誌 4104:長指令碼如何被拆開、又如何拼回去

有一種還原後的 PowerShell 完全不需要解碼:機器自己給你的。當指令碼區塊日誌(Script Block Logging)開啟後,Windows 會把 PowerShell 處理的每個指令碼區塊的內容記為 Microsoft-Windows-PowerShell/Operational 日誌裡的 4104 事件——而且記的是 PowerShell 引擎看到的程式碼,也就是 -EncodedCommand 和混淆層已經被引擎展開之後的樣子。一段以 Base64 形式到達的可疑單行命令,在 4104 事件裡就是它實際執行的可讀指令碼。這就是為什麼這個功能是防禦者能開啟的最有價值的 PowerShell 日誌設定,同時也是為什麼它預設是關的:它很囉嗦,日誌裡還可能含有認證和其他敏感資料,所以微軟建議除診斷用途外配合受保護事件日誌(Protected Event Logging)一起使用。

麻煩在於,Windows 會把長的指令碼區塊拆成多個 4104 事件。每個事件帶著同一個 ScriptBlockId、一個 MessageNumber 和一個 MessageTotal——比如 5 段裡的第 2 段——完整的指令碼只有把這些事件按 ID 分組、按訊息號排序、拼接起來才存在。單獨看一個事件,可能在一個詞中間就斷了;只讀第一段的搜尋,會漏掉負載末尾做的事。所以拼裝是一個機械步驟,只有一件事要檢查:有沒有缺段。如果日誌說 3 段裡有 2 段,而第 2 段始終沒到——因為日誌輪轉了,或者被清空了——拼出來的文字就是不完整的,基於它得出的任何結論都只是暫時的。同樣的分組邏輯也是EVTX 檢視器在匯出的 .evtx 檔案裡標記 4104 事件時用的辦法;另見配套文章在沒有 Windows 機器時讀取 .evtx 檔案。

讀懂指標:下載搖籃、AMSI 繞過、執行原則繞過

指令碼變得可讀之後,下一步不是宣布它惡意——而是把裡面的指標當作待驗證的線索來讀。下載搖籃——DownloadString、DownloadFile、Net.WebClient、Invoke-WebRequest、bitsadmin、certutil 從某個 URL 拉檔案——告訴你這個指令碼會從某處取程式碼,下一個問題就是從哪裡取、那個目的地在你的網路裡是否正常。AMSI 繞過——amsiInitFailed、AmsiUtils,或給反惡意軟體掃描介面打補丁的程式碼——告訴你作者希望指令碼在不被掃描的情況下執行,這是一個強訊號,但不是定論:滲透測試人員和一些合法工具也會這麼做。執行原則繞過(-ExecutionPolicy Bypass、-ep bypass)告訴你作者不想讓 PowerShell 自帶的防護攔住指令碼,而這在惡意軟體和執行未簽署指令碼的日常自動化裡都很常見。

紀律在於把每個指標當作問題,而不是答案。下載搖籃加隱藏視窗(-WindowStyle Hidden)再加編碼命令,是一個值得上報的模式;而其中任何一個單獨出現,都只是值得檢查的模式。把線索變成定論的是上下文:是誰啟動了這個行程、它的父行程是什麼、它聯絡的 URL 是否已知、同樣的指令碼是否出現在別的機器上。一個列出指標、貼出比對片段、並寫著「請結合上下文驗證」的還原工具,做的是誠實的工作;而一個僅憑模式清單就給指令碼蓋上「惡意」或「乾淨」印章的工具,則誇大了靜態分析能知道的東西。反過來也一樣:沒有發現任何指標的指令碼並沒有被證明安全——它可能只是用了指標清單沒涵蓋的技術,或者在執行時動態拼裝負載。

安全工作流程:不要貼到聊天裡,不要執行,惰性解碼

處理可疑 PowerShell 指令碼最重要的一條規則是:絕不要執行它來看看它會做什麼,也不要把它貼到聊天視窗裡問 AI。執行它就是執行負載——而這正是你要避免的結果。貼到聊天裡會把一段可能惡意的指令碼發給第三方服務,留在你無法控制的對話記錄裡,而且常常還會遭到拒絕,因為 AI 系統通常拒絕還原可能惡意的指令碼。安全的工作流程是故意做得很無聊的:把亂碼複製到瀏覽器本地的還原工具裡,或者一台沙箱分析機上,把解碼輸出當作惰性文字來讀,絕不把它當作程式碼來執行。

接下來的流程是一張很短的清單。第一,逐層解碼,直到文字不再變化,並記下還原是在哪裡停下的。第二,讀懂指標,把每一條如果屬實意味著什麼寫下來,再逐一對照上下文驗證——父行程、目標 URL、它來自哪台機器。第三,把結果交給為下一步而設的人和系統:你的 EDR 控制台、一次沙箱引爆、你的事件回應團隊。還原工具的工作到可讀文字和誠實的指標為止;定論屬於沙箱和分析人員,不屬於解碼工具。如果指令碼來自你管理的機器,檢查一下指令碼區塊日誌是否開著——如果開著,4104 事件就是它實際執行內容的真相,值得在日誌輪轉之前先收集起來。

來源

常見問題

還原一段可疑的 PowerShell 指令碼安全嗎?

只要解碼是純靜態的——把亂碼當作文字處理、絕不執行——就是安全的。瀏覽器本地的還原工具解開 Base64、解壓縮 gzip、解析字元碼、拼接字串,都只是在重新排列文字,什麼都不會執行。不安全的是把指令碼貼到終端機裡、雙擊 .ps1 檔案,或者貼到聊天服務裡——前者會執行它,後者會把它發給第三方。讓解碼保持惰性,把解碼輸出當作要讀的文字。

-EncodedCommand 解開了,結果看起來還是編碼過的,怎麼辦?

這是正常的:真實負載會疊加多層。解出的文字本身可能又是一段包了 Base64 的 gzip 壓縮資料,或一段由 [char] 碼、拼接、-f 格式運算子拼出來的字串。一層一層剝——解碼,再檢查壓縮,再解析字元碼和拼接——直到文字不再變化。如果文字不再變化時仍然看不懂,負載很可能被加密了,或是在執行時動態拼出來的,靜態還原已經到極限了:這是一條要交給沙箱的發現,不是工具的失敗。

結果乾淨就代表指令碼安全嗎?

不代表。結果乾淨只說明沒有已知的指標模式命中、所有能還原的層都還原了——並不能證明指令碼無害。負載可能被加密、可能在執行時用只有執行中的機器才知道的值拼字串,也可能只是用了指標清單沒涵蓋的技術。把乾淨的結果理解為「靜態分析沒有發現東西」,在下結論之前先在沙箱裡或用 EDR 驗證。反過來也一樣:一個指標是一條待驗證的線索,不是定論。