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にデコードされることを期待します。1文字2バイト、各ASCII文字の後にゼロバイトが続く。これはWindows上の.NET文字列のエンコーディングだからです。無害なコマンドWrite-Host "hello"のBase64はSQB3AHIAaQB0AGUALQBIAG8AcwB0ACA…で始まり、繰り返し現れる「A」という文字が、無数のゼロバイトの痕跡です。

これを知ると、エンコードされた塊の読み方が変わります。UTF-8の意味不明な文字列にデコードされるBase64は、おそらくそもそもUTF-8ではありません。まずUTF-16LEを試してください。「意味不明」は読めるPowerShellに変わります。-EncodedCommandスイッチが見当たらない裸のBase64塊も同じです。印刷可能なテキストにデコードされる有効なBase64の長いトークンは、フラグなしの同じ手口です。実用的なルールは単純です。PowerShellのコマンドラインで長いBase64トークンを見たら、デコードで反証されるまではスクリプトだと想定し、チャットウィンドウやターミナルに貼り付けて様子を見るのではなく、決して実行しないツールで惰性テキストとしてデコードしてください。

層の重ね合わせ:圧縮、文字コード、連結、-f、バッククォート

実際のペイロードが1層で止まることはめったにありません。-EncodedCommandをデコードすると、その下のスクリプト自体がさらにエンコードされていることがよくあります。gzip圧縮された塊がさらにBase64文字列で包まれていたり、[char]68+[char]105のような文字コードの列が1文字ずつコマンドを綴っていたり、文字列が断片に切り刻まれてプラス記号でつなぎ直されていたり、-f演算子が実行時に引数をフォーマット文字列に埋め込んでいたり、キーワードにバッククォートが散りばめられてDo`wnloadStringがDownloadStringの単純な検索に引っかからなくなっていたりします。各レイヤーはそれぞれ異なる理由で存在します。圧縮は長いペイロードを短いワンライナーに縮め、テキスト検索を完全に無力化します。文字コードと連結は、疑わしい単語がファイル内に文字通り現れないようにすることでシグネチャ照合を無力化します。-f演算子とバッククォートは、スクリプトを構文的に有効なままにしながら単純な文字列検索を無力化します。

そのすべてに対する防御は同じです。順番に1層ずつ展開し、適用できるものがなくなったら止めます。Base64をデコードし、結果が圧縮されていないか確認して展開し、文字コードを解決し、断片をつなぎ、フォーマット引数を代入し、バッククォートを除去します。各ステップは次のステップが処理できるプレーンテキストを生み出します。これを目に見えるタイムラインとして表示するツール、層ごとに「剥がす前」と「剥がした後」のカードを示すツールは、最終結果だけを黙って出力するツールより価値があります。タイムライン自体が証拠だからです。どのレイヤーが存在したかが分かり、展開が途中で止まったとき——「これ以上展開できません」——それは発見であり、失敗ではありません。ペイロードに、静的解析だけでは到達できない動的なもの、暗号化されたもの、実行時に組み立てられるものが含まれていることを教えてくれます。

スクリプトブロックログ4104:長いスクリプトが分割され、再構成される仕組み

デコードをまったく必要としない難読化解除済みPowerShellの情報源があります。それはマシン自身です。スクリプトブロックログが有効になっていると、WindowsはPowerShellが処理するすべてのスクリプトブロックの内容を、Microsoft-Windows-PowerShell/Operationalログのイベント4104として記録します。しかも、エンジンが-EncodedCommandと難読化レイヤーをすでに展開した後の、PowerShellが見た通りのコードを記録します。Base64として届いた疑わしいワンライナーは、4104イベントの中では実際に実行された読みやすいスクリプトとして現れます。だからこそこの機能は、防御者が有効にできる最も価値の高いPowerShellログ設定であり、同時にデフォルトで無効な理由でもあります。冗長で、ログに認証情報などの機密データが含まれる可能性があるため、Microsoftは診断用途以外では保護イベントログ(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フォーマット演算子で組み立てられた文字列だったりします。1層ずつ展開してください。デコードし、圧縮を確認し、文字コードと連結を解決し、テキストが変化しなくなるまで続けます。変化しなくなってもまだ読めない場合、ペイロードは暗号化されているか、実行時に動的に組み立てられている可能性が高く、静的展開は限界に達しました。それはサンドボックスに渡すべき発見であり、ツールの失敗ではありません。

結果がクリーンならスクリプトは安全ということですか?

いいえ。クリーンな結果は、既知の指標パターンに一致するものがなく、展開できる層はすべて展開されたことを意味します。スクリプトが無害であることを証明するものではありません。ペイロードは暗号化されているかもしれませんし、実行中のマシンだけが知る値から実行時に文字列を組み立てているかもしれませんし、単に指標リストに載っていない技術を使っているだけかもしれません。クリーンな結果は「静的解析では何も見つからなかった」と受け止め、何かを決める前にサンドボックスやEDRで検証してください。逆も同じです。ひとつの指標は検証すべき手がかりであり、断定ではありません。