静的分析によるPowerShellスクリプトの実用的振る舞いプロファイリング (3)

This post is also available in: English (英語)

概要

3部構成となる本シリーズでは、PowerShellスクリプトの静的分析に対する実用的アプローチと、この静的分析実行のためのプラットフォーム非依存型Pythonスクリプト開発に焦点を当てています。本稿は第3回です。第1回はこちらで、第2回はこちらで確認してください。

シリーズ最後となる本稿では、サンプルにプロファイリング用スクリプトを実行し、出力された内容を解釈する方法を説明します。また、今回静的分析用スクリプトを作成して得られた全般的な知見について解説し、皆さんの組織でこのスクリプトをどんなふうに活用できそうかについて考察し、最後に本シリーズを通してこれまで説明してきたPowerShellProfiler.pyスクリプトを提供して内容をまとめたいと思います。

はじめに

テストでは、手で分類した約5,000個のPowerShellスクリプトサンプルセットを使用しました。その内訳は、約3,000個の良性スクリプトと2,000個の悪意のあるPowerShellスクリプトです。筆者は次に、これらに含まれる振る舞いをプロファイリング用に分類していきました。そのさい、振る舞いのスコアリングをする上での調整役となりうるような、スクリプトの特徴その他の機能についても特定していきました。

まずはこの方法でベースラインを確立することで、多用される手法や結果の出やすい手法に慣れ親しみつつ、そこから得られた結果をプロファイリング用スクリプトに落とし込みました。これにより、残りの分析時間の大半を、目標値であるスコアリングしきい値6.0を下回った悪意のあるスクリプトサンプルの分析にあてることができました。そうしたサンプルからは、プロファイリングのプロセスをさらに強化できそうなポイントを見つけ出していくよう努めました。

PowerShellProfiler.pyの使いかた

本稿のプロファイリング用スクリプトは、Unit42 GitHubから取得できます。この中にPowerShellProfiler.pyというスクリプトがあります。使いかたは、入力用ファイルを -f フラグに続けて指定するだけです。これにより以下のような出力が生成されます。

スクリプトの出力結果は、次のような構造になっています。

ほとんどのフィールドの内容は、名前から自明かと思います。また、さっと確認できるよう、出力結果は極力シンプルにしてあります。

「Proposed Risk Rating (提案されたリスク評価)」のフィールドは、このシリーズの第1回目で説明したリスク階調スケールから導出されたもので、これにはアナリストがぱっと見てリスクが何であるかが分かるようにするガイドとしての役割があります。

「Score (スコア)」フィールドは、出力の最後にある「Behaviors (振る舞い)」フィールドに表示されたスコア (正と負)を集計したものです。この例でも見られるように、難読化の種類や既知のマルウェアファミリなど、コンテキストが追加で提供される振る舞いもあります。

オプションのデバッグフラグ(-d)を使うと、デバッグ情報を出力できます。デバッグフラグは、分析対象のスクリプトがどのように変更されたかを確認したり、復元されたコンテンツを確認したりするのに役立ちます。デバッグ情報は、単純に正規化と難読化の解除後に検出されたコンテンツをすべて出力しているだけなので、解明の必要な新たなコード隠ぺいテクニックを探すためにどこを見るべきか、そのアタリをつけるのに使うとよいでしょう。

難読化を1ステップずつ読み解く

さて、このサンプルは何を実行しているように見えるでしょうか。上記の「1b987ba4983d98a4c2776c8afb5aebbe418cdea1a7d4960c548fb947d404e4b2」というSHA256ハッシュ値を持つファイルの元コンテンツからは、何も処理しなくても、2つの振る舞いが存在することがわかります。Base64文字列をdeflateした後で実行するのに使うStreamオブジェクト関連の.NETメソッドが複数あります。"Convert"、"FromBase64String"、"Text.Encoding"などのキーワードは、「Compression (圧縮)」の振る舞いフラグをトリガーし、"Invoke-Expression"というキーワードは「Script Execution (スクリプト実行)」の振る舞いをトリガーします。

図9 元のPowerShellスクリプト
図9 元のPowerShellスクリプト

プロファイリング用スクリプトは、コンテンツをdeflate後、処理をループして追加のコンテンツを正規化し、難読化を解除し、追加コンテンツを表出させます。

図10 展開されたスクリプトのコンテンツ
図10 展開されたスクリプトのコンテンツ

この例では、文字列を隠ぺいするためにフォーマット演算子置換の難読化手法が多用されていることがわかります。さらに処理のなかで文字列置換用の関数を多数実行しようとしています。プロファイル用スクリプトの実行終了時の結果を以下に示します。

図11 難読化解除後のPowerShellスクリプト
図11 難読化解除後のPowerShellスクリプト

ここまでくると残った振る舞いを確認できます。キーワード"downloadstring"は「Downloader (ダウンローダ)」の振る舞い、"Start-Process"は「Starts Process (プロセス開始)」の振る舞い、最後の"$env:username"は「Enumeration (列挙)」の振る舞いをトリガーします。

最後の2つの振る舞いは、特定キーワードの組み合わせではなく、コンテンツの構造により重点を置いたものです。メタ的な振る舞いである「One Liner (ワンライナー)」の場合、元のファイルが1行に収まっていることからフラグがトリガーされています。これに対し、「Known Malware: Veil Stream (既知のマルウェア: Veil Stream)」という振る舞いは、元のPowerShellスクリプトにある次の2行に基づいてトリガーされています。

これらは以前の調査で特定したGitHub上のVeil FrameworkがPowerShellペイロードを生成するさいに見られる内容です。

このスクリプトのスコアは6.0という目標しきい値をはるかに超えているので、「悪意のある可能性が高い」とスコアリングされます。

静的分析全般に関する考察

ではここで、本スクリプトの検証で得た知見全般についてかいつまんで説明し、サンプルによってはプロファイリングに適さない場合があること、このプロファイリング用スクリプトでは悪意のある活動で目標としているしきい値に届かないケースがある理由について説明します。

  • コンテンツのリモートからの読み込みは、高度な攻撃でよく見られる戦術です。その目的は、ペイロードを表に出さないことで分析を回避することにあります。そうしたケースでは、あまりに汎用的な「ダウンローダー」以上の振る舞い指標がトリガーされず、意図の推測には役立たないかもしれません。「Negative Context (ネガティブなコンテキスト)」に記載したキーワードを使用することが唯一実行可能な調整となる場合がありますが、この手の単語リストの管理は悪夢なので、あくまで最後の手段として使用すべきです。
  • 複数のスクリプト言語が組み合わせで使われている場合も、意図を決定する上で問題となることがあります。このプロファイリング用スクリプトは、PowerShellスクリプト以外の言語固有の構造の難読化を解除したり、正規化したりできるようには設計されていません。Base64や型変換などより汎用的な難読化であればうまく動作してコンテキストの手がかりを明らかにしてくれるケースもありますが、通常はあまりあてにすべきではありません。JavaScriptやVBSなど、PowerShellと組み合わせて使用されるスクリプト言語では、特定の機能のためにPowerShellを利用したり、その逆でPowerShellがそれらの言語の特定の機能を利用することが多々あります。複数種類のスクリプト言語を混ぜて使っているスクリプトも対象とするには、このプロファイル用スクリプトをそれらのスクリプト言語にも対応できるよう拡張しなければなりません。そうでないかぎり、すべての振る舞いが可視化されず、一部に盲点が生じてしまいます。
  • 複雑な正規表現パターンの使用には一長一短があります。PowerShellに見られる膨大なバリエーションに対応するために、正規表現パターンは意図的に非常にゆるくしてあります。このためにマッチングにミスが発生することはありえます。ここでいうミスには、プロファイリングで見落とされるコンテンツが発生することや、過度なマッチング、せっかくマッチしたコンテンツが変更されてしまって期待した振る舞いと一致しなくなる、などが含まれます。これは、難読化の種類に関係なくキーワードを識別することと、誤検出(偽陽性)を回避することとの間で、うまくバランスのとれる場所を見つけようとした結果生じたものです。
  • 一般的にいって、PowerShellの柔軟性を悪用して極端な難読化手法を使えば、プログラム的に静的分析を行うことは非常に難しくなります。その手のサンプルでは、すぐそれとわかるメタ的な特徴を特定できることが多いですが、そうしたスクリプトの本質がスコアに反映されないこともあるのでそこは注意が必要です。
  • 表層上は「悪意のあるスクリプト」の定義に当てはまらないように見えることから、スコアが高くならないPowerShellスクリプトも存在します。ただし、そうしたスクリプトのなかには、特定の見地からすれば悪意があるものと見なされうるものがあります。たとえば、PCが起動するたびにInternet Explorerのホームページを変更するPowerShellスクリプトや、ネットワークのプロファイリングを行うPowerShellスクリプトなどがそれにあたります。これら例はいずれも高レベルでは悪意のあるなしで賛否が分かれるものと思います。確かにコンテキストは重要なのですが、そのコンテキストじたい、実は出力内容を解釈する人次第だ、ということに気づかせてくれもします。
  • Microsoftが作成する機能により、また攻撃プラットフォームとして、PowerShellは進化しつづける言語です。今後、より攻撃的なツールが公開されたり、新しい機能が採用されたりすれば、振る舞いのキーワードは古くなっていくことでしょう。ですから、経時的状況変化を常に把握するには、つねにこれらの動向に注意を払っていなければなりません。
  • スコアリングに一律な判定を使わず、リスク階調スケールを使う場合、そこに解釈のあいまいさが生じ、プロファイリング用スクリプトの変更による影響を測定することがさらに難しくなります。筆者は開発段階で「グラウンドトゥルース(正しい答え)」のサンプルセットを大いに活用し、振る舞いスコア値の変化、新しい振る舞いの追加、表に出てきたコンテンツ、難読化の解除機能の追加が個々のファイルにどのような影響を与えるかを確認してきましたが、目標のしきい値が6.0と比較的低いため、どのような変更をした場合でも、サンプルを誤った方向に導くほど大きな影響を与える可能性がありました。

ScriptBlockログと実用的な使用例

これまでの説明を総括し、PowerShellプロファイリング用スクリプトを実地ではどう活用すべきでしょうか。本シリーズを締めくくるにあたり、活用方法のヒントをいくつか紹介します。

まず一番わかりやすいのは、インシデントレスポンスの初期段階でこのスクリプトを実行して、PowerShellスクリプトに存在しうる振る舞いを把握する、という用途でしょう。こうして確認した振る舞いをもとにリサーチの方向性やとるべき防御策を決定するのに使えると思います。たとえば、対象サンプルが「Downloader (ダウンローダー)」の振る舞いを示したのであれば、ただちにスクリプトから呼び出されるドメインやIPアドレスを集中的に確認することで、それらの活動内容が残っているネットワークログの探索を始めることができます。同様に、「Enumeration (列挙)」や「Persistence (永続化)」などの振る舞いが見られたのであれば、そのPowerShellスクリプトの内容からレジストリキーや値、ファイル名、ファイルパスなどの独自の機能が明らかになり、その情報をもとにさらにリサーチを進めることができるでしょう。

このほか、プロファイリング用スクリプトを使って、社内にあるPowerShellスクリプトに一括スキャンをかける、という活用方法も考えられます。そのさいは、プロファイリング用スクリプトを各ホストシステムに展開し、ローカルで実行させることができます(ただしこの方法はあまりお勧めはしません)。あるいは、社内にあるPowerShellスクリプトをすべてバックアップ用の中央集中管理リポジトリのような場所に集め、そこでスキャンさせることもできます。ただしその場合、バックアップ対象になっていない場所にあるPowerShellスクリプトは見逃される可能性が残ります。いずれの方法で収集するにせよ、GNU Parallelなどのツールを使用すれば、数十万単位のファイルでも比較的短時間で処理できることでしょう。筆者が検証で使ったサンプルセットの場合、個々のPowerShellスクリプトの平均処理時間は約2ミリ秒でしたので、利用できるリソースしだいでうまくスケールできると思います。ファイルの処理が済んだら、スコアや振る舞いの組み合わせをもとに、どのファイルの調査をさらに進めるべきか、すばやく判断することができます。

最後に、ScriptBlockログについて説明します。ここでは、「なにかしらの理由があってローカル分析をするためにスクリプトを集めるのは手間がかかりすぎるが、自社にはエンドポイントからログを取り込んでいるSIEMなどのログ収集システムがある」ものと仮定しましょう。そのような組織であれば、社内には少なくともPowerShellのバージョン5.0以降が導入されているものと思います。MicrosoftはPowerShellのバージョン5.0以降でスクリプトのトレースやログ記録の機能を導入しています。ご存知でないかたのために補足しますと、ScriptBlockログをホスト上で有効にすると、PowerShellがコンパイルして実行するコードブロックのログをETWイベントログに記録できるようになります(Event ID 4104)。

ScriptBlockログの主な利点の1つは、PowerShellがコードをコンパイルして実行時するさいに、ここでプロファイリング用スクリプトが静的にやろうとしている難読化レイヤーの削除もやってくれることです。ScriptBlockログのもう1つの利点は、自社システム全体から経常的にイベントのストリームを取り込める点です。これだと、ほぼリアルタイムでイベントをチェックできるようになりますし、侵害された可能性のあるホストをすばやく特定するための専用アラート作成も可能になります。

ただし、ScriptBlockログにもそれなりに問題点はあります。その筆頭が、必ずしもプロファイリング用の完全なスクリプトを利用できるわけではない、という点でしょう。このほか、静的分析が動的分析より優れている点として、静的分析であればPowerShellスクリプトが実行する実行パスに制約されないという点が挙げられます。結果として静的分析のほうが対応できる範囲が広くなります。一方のScriptBlockイベントは、PowerShellが実行したコードのみが対象となります。動的解析の2つ目の問題は、ある特定のケースに出現した振る舞いをすべて適切に識別するには、PowerShellがログに記録した完全なコードブロックのセットが不可欠、という点です。イベントが含むさまざまなメタデータを使えば、ScriptBlockイベントを相関させることもできなくはないのですが、それらをプロファイリング用にまとめるのは少々骨が折れます。

以上を踏まえ、以下のスクリーンショットはシリーズ第2回の図8で示したのと同じサンプルについてのScriptBlockイベントです。これを見れば、今回のプロファイリング用スクリプトが、展開、フォーマット演算子置換、文字列置換などのすべての処理を実行したPowerShellの実行結果にかなり近づいていることがわかると思います。

図12 難読化が解除されたScriptBlockイベント
図12 難読化が解除されたScriptBlockイベント

ただしScriptBlockログの場合、次のコードブロックが実行されたことを表示できる追加のメリットがあります。これは、プロファイリング用スクリプトでは見つけられなかったデータです。このケースでは、正規表現パターンを使用してURL構造とマッチさせてマルウェアファミリを識別するなど、ここからさらにプロファイリングを続けていける可能性があります。

図13 ScriptBlockイベントによって明らかにされた追加のデータ
図13 ScriptBlockイベントによって明らかにされた追加のデータ

結論

静的分析にも欠点がないわけではありませんが、コツさえつかめば、悪意のあるPowerShellスクリプトの検出能力向上に一役買ってくれます。

ここで提供した3部構成シリーズとPowerShellProfiler.pyが、皆さんの調査力向上のヒントや手段として役立つものとなったことを願っています。結局のところ筆者個人は、どんなツールを使ってもいいので皆さんがよりピンポイントにリサーチ対象領域を絞り込めるようになればそれで満足です。

本稿で解説したPowerShellProfiler.pyは、Unit 42のGitHubページからダウンロードしてください。