This post is also available in: English (英語)
概要
3部構成となる本シリーズでは、PowerShellスクリプトの静的分析に対する実用的アプローチと、この静的分析実行のためのプラットフォーム非依存型Pythonスクリプト開発に焦点を当てる予定です。本稿は第2回です。第1回はこちらで確認してください。
本シリーズでは、振る舞いプロファイリングの詳細、PowerShellスクリプトでよく見られる難読化方法、データ隠蔽方法、スクリプトのリスク評価のためのスコアリングシステム構築方法についても説明していく予定です。本シリーズの目的は、セキュリティアナリストや組織のサイバーセキュリティ担当者が実用的なスクリプティングの基礎と考えかたを身につけられるようにすることにあります。
はじめに
シリーズ第1回では、静的分析、PowerShellの動作、そして本稿で焦点となる設計上の考慮事項についての全般的概念について触れました。本稿での最終的な目標は、振る舞いをプロファイリングし、PowerShellスクリプトにこめられた意図を推測することです。そこで、最初にスクリプトの入力を確認することから始めたいと思います。
次に、インターネット上でよく見られるPowerShellの難読化・コード隠蔽のテクニックを解除・正規化するための一般的手順を紹介し、スクリプトのコンテンツがこうした手順を経てどのように変わっていき、より多くのデータが明らかになっていくかを、実例によって示したいと思います。
最後に、スコアリングにとって重要であると特定した振る舞いと、そうした振る舞いがスコアリングにおいてどのようにリスクの全体的評価に影響するのかについて見ていきます。
PowerShellスクリプトの入力
入力にはさまざまな種類がありますが、話を簡単にするため、ここではPowerShellスクリプトに限定してお話します。ScriptBlock Logs、VBScript、JavaScriptなど、PowerShellコマンドや埋め込みスクリプトを含むことのある他の種類のファイルについては取り上げません。ただし、ファイルの種類に関係なく、最初の手順はいつも同じ、スクリプトの残りの部分で処理するデータを準備することから始まります。
今回の最初の手順はかなりシンプルです。PowerShellは、関数名やキーワードなどを定義するのにASCIIコード範囲の文字セットを使いますが、このASCIIコード範囲の文字セットに対する正規表現や文字列マッチングを今後頻繁に行う予定なので、最初に入力データからNULLバイトを削除しておき、分析が進んでから文字エンコーディングが問題にならないようにしておく必要があります。
デフォルトのWindows用コードページやUnicodeについてあまりよく知らないかたは、さしあたって「1文字あたり2バイト使って文字を表すんだな」ぐらいに考えておいてください。なお、今回とりあげる実例で関心があるのはASCIIコード範囲におさまる文字のみです。文字エンコード処理をする2バイトのコードページの場合、1バイト目がNULLになります。たとえば、"HELLO"という単語との完全一致を検索したい場合、このASCIIコード範囲におさまる文字列を表す各バイトの先頭にはNULLバイトがついています。これではマッチング上不都合が生じることがありますので、先に削除しておくのです。これにより、文字列“\x00H\x00E\x00L\x00L\x00O”はシンプルな“HELLO”になります。こうしておくことで、後の段階のデータ処理に移る前に、マッチさせる文字を均しておくことができます。
プロファイリングのためのコンテンツ準備
PowerShellの振る舞いを正確にプロファイリングするには、できるだけ多くのコンテンツを正規化し、難読化を解除しておく必要があります。そうすることで、コンテンツの振る舞いを識別するチャンスを最大化できるからです。こうしたコンテンツ内の振る舞いには、見ただけではっきり分かるケース、複数の難読化レイヤーの下に隠されてしまっているケース、さまざまなエンコードアルゴリズムで隠蔽されているケースなどがあります。
この処理を行うため、ここで作成するプロファイリング用スクリプトは、元のコンテンツから2組のデータを作成し、それらを連続的にスキャンするようにしてあります。1組目は、元のコンテンツを含み、上で説明したNULLバイトを除いたもの、2組目は、処理中に発見された新しいコンテンツを元のコンテンツに追加していくものです。なかには元のコンテンツからの逸脱がほとんど見られないケースもありますが、この逸脱が多いか少ないかは、使用されている難読化技術や隠ぺい手段の特定状況次第です。
プロファイリングのためのコンテンツ準備は、はっきり2段階に分かれます。1つめは、コンテンツをクリーンアップしてさまざまな種類の難読化を解除する段階です。2つめは、暗号化、圧縮、エンコードなど、PowerShellがサポートするコード内にデータを埋め込むさまざまな方法を解明する段階です。今から、これらの各段階を1ステップずつ解説し、各段階での目標を達成するべくスクリプトに組み込んだ現在の機能についてレビューしていきますが、先に進む前に、難読化に関する概要と、これからどのように処理を進めていくかのアプローチについて説明しておきたいと思います。
悪意のあるスクリプトを分析していれば、単一スクリプト内に何層にもわたり、何種類もの難読化が行われているケースによく突き当ります。これが何を意味するかというと、いったんスクリプトでコードの難読化を解除しても、その下から新しいコードが出現する可能性がある、ということです。その場合、さらに難読化が含まれていないか、さらに正規化する必要がないかを確認するため、同じ処理を繰り返して再分析にかけなければなりません。これを踏まえると、なにかコードの状態や続くコードの更新状況を追跡する方法が必要です。つまり、コードの状態に変化が見られなくなるまで、スクリプトの処理を続行させるわけです。このワークフローを示したのが次の図です。
高レベルでは比較的シンプルに見える処理フローですが、実際には、スクリプトがさまざまな種類の難読化を処理する順序など、実用上の問題が発生する懸念もあります。こうした懸念や、スクリプトが難読化の解除や正規化を実行する順序については、繰り返し検証を行って導き出します。
プロファイリング用スクリプトが解析対象となっているコードを実行することはないので、機能するスクリプトを復元することはさほど重要でなく、むしろ生のコンテンツに戻すことが重要です。ただし、プロファイリング用スクリプトで難読化を解除するさいは、できるだけ元のコードとの整合性を維持するようには努めました。コードに状態変化が見られなくなったら、プロファイリング用スクリプトはいよいよ振る舞いのプロファイリングを行う段階に進むことができます。
今回とりあげる例では、コンテンツの正規化や復元を行う関数が多数あるので、それぞれについて詳しく説明し、その意図を説明し、それらがコードに与える影響について説明します。
正規化/難読化の解除
一文字単位以外の難読化を解除する場合、その難読化の識別には通常、単純な検索や正規表現マッチを使います。PowerShellは非常に柔軟性が高いので、同じコマンドを実行するのに、多数のバリエーションが存在するのを目にすることになると思います。つまり、ここで作成した正規表現のパターンも、なるべく多くのバリエーションを捕捉できるよう設計はしたものの、そこには常に改善の余地がありますし、スクリプトに含まれるパターンも、私が用意したサンプル セットで見つかったものに限られているということを忘れないでください。このことを説明するのに、Format String Operator Replacementと呼ばれる種類のテクニックをキャプチャする次の正規表現パターンについて考えてみてください。
1 2 |
\((?:\s*)(\"|\')((?:\s*)\{[0-9]{1,3}\}(?:\s*))+\1(?:\s*)-[fF](?: \s*)(\"|\').+?(\"|\')(?:\s*)\)(?![^)]) |
これは次のような内容と一致させることができます。
1 2 3 |
("{1}{0}" -F"exa" ,"mple") ( " {0} " -F "example") ( "{1} {0} " -F 'exa' , "mple" ) |
基本的な一文字単位の難読化は、処理中にコンテンツから解除されます。これに対し、より複雑な難読化の場合はコンテンツのブロックがインラインで置き換えられます。処理される難読化の種類ごとに、簡単な説明と、実際の動作例と、それが何に変換されるべきかについて示します。
1. バックスラッシュ(`) は、PowerShellでは文字エスケープやコード行を折り返すために使用します。特殊文字以外の文字をエスケープしたり、単語を分割してマッチさせないようにするのは、難読化でよく使われる方法です。
1 2 |
if ( ${CoM`P`U`TERn`AME} -eq ${Nu`LL} if ( ${CoMPUTERnAME} -eq ${NuLL} |
2. キャレット(^)はWindowsコマンドラインのエスケープ文字で、複数のスクリプト言語を混ぜて使っているケースでよく目にするものです。
1 2 |
echo i^eX(^"^I^e^`X^` echo ieX("Ie`X` |
3. 引用符 (\”)のエスケープは、部分文字列をエスケープしなければならない場面で最もよく目にするものです。エスケープを削除し、バックスラッシュがパターンマッチに影響するような境界をまたいだ文字列についても、正確にプロファイリングできるようにします。
悪意のあるスクリプトでは、こうした部分文字列の多くはコマンドで、そうしたコマンドは復元されるか、さらにプロファイリングの対象となりうる新しいPowerShellインスタンスに渡されます。また引用符のエスケープは、次の例のように、追加で難読化を行うために空の引用符を挿入する目的にも使用されることがあります。
1 2 |
(g\'\'v KUs).value.toString() (g''v KUs).value.toString() |
4. このほかPowerShellには、変数を分割するため、あるいは文字列のマッチを避けるために空の引用符('')を使うという手法もあります。これも必要に応じて削除します。
1 2 |
(g''v KUs).value.toString() (gv KUs).value.toString() |
5. スペース( )も、CaMeL CaSe(キャメルケース)のキャピタライズに似た方法で難読化を行う用途に使えます。スペースは、PowerShellによるコードの解釈には影響を与えることなく、コードのレビューアを混乱させる目的で使用されます。
1 2 |
-EX uNrEsTRIcteD -nOP -W HIdDEn -eC-EX uNrEsTRIcteD -nOP -W HIdDEn -eC |
6. 文字列の連結(+)も、文字列を分割するための一般的な手法のひとつです。そのため、プロファイリング用スクリプトではこれらの文字列の再構築を試みます。文字列の連結にはさまざまな方法がありますが、この例では加算記号(+)を使った方法に的を絞っています。
1 2 |
New-Object $("Sys"+"tem.Refl"+"ection.Ass"+"embl"+"yName") New-Object $("System.Reflection.AssemblyName") |
- 型変換も文字列の難読化によく使用される手法なので、このプロファイリング用スクリプトでも探していきます。この探索を行うのは、私のプロファイリング用スクリプトでは最初の「ブルートフォース型」関数群です。ここでは、複数のベース値(base8、base16、base32)を反復処理して文字列となりうる値を作成し、それと同時に、リストに格納されている整数値や16進値のなかにASCII文字列に変換可能なものがあるかどうかの特定を試みます。
1 2 |
(‘6e,6f,74,65,70,61,64'.SPLiT(‘,’) |fOREAch {( [cHar]([COnVERt]::tOINt16(([STRINg]$_ ) ,16 ))) })-jOIn '') Notepad |
- 上の例のように、分割と変換は連動しています。ある範囲の文字を使用し、整数や16進値を分割するというこの種の難読化は悪意のあるスクリプトでは頻繁に見られます。このような場合、このプロファイリング用スクリプトは下手な鉄砲も数撃ちゃ当たる方式で、とにかく連続する値のセットを取り出してできるだけ多くのプレーンテキストを解読しようとします。
1 2 |
27R2cQ20i27p27{29hdQa{7dpd~a'.SPLiT('{p}hiRQ~' )| 27 2c 20 27 27 29 d a 7d d a |
このプロファイリング用スクリプトが処理する難読化の手法はこのほかあと2種類あります。これら2種類は、PowerShellによる機能呼び出し方法が柔軟であるがために、識別や分析の面で少々複雑になります。
7. Format String Operator Replacementという難読化手法の利用が悪意のあるスクリプトの間で急速に広まっています。これはDaniel Bohannon氏がInvoke-Obfuscationを公開して以来の傾向で、同ツールでFormat String Operator Replacementが多用されているのです。正規表現を使用してこれらを静的に解析する関数群では、何層にもネストした演算子の置換を考慮しなければならないので、まずは内側にあるより小さな文字列を処理対象とし、徐々に内から外へと復元を進めるようにしています。これらの関数群では、パースした文字列を置換コマンドで置き換え、この処理をコンテンツがそれ以上変化しなくなるまで反復します。
1 2 |
('V'+("{1}{0}" -f 'b',("{1}{0}" -f 'A','aRi'))+'Le:' ('VaRiAbLe:' |
8. プロファイリング用スクリプトは、PowerShell組み込みの文字列置換関数についもやはり正規表現のパターンを使用します。文字列置換関数が生成可能なバリエーションをできるかぎりたくさん識別し、コンテンツ内のすべての文字列の置換を試みます。
1 2 |
OUT-fILe ("C:c4yprogramdatac4yerror.txt").rEpLAce(("c4y"),'\')' OUT-fILe ("C:\programdata\error.txt") |
こうした難読化の解除や正規化を行う一連の関数は、新しいコンテンツが検出されたり、既存データの状態に変化が見られる限り、何度も繰り返し実行されます。
コンテンツの復元
プロファイリング用コンテンツの難読化解除や正規化を実行する上記関数群と同様、このスクリプトにはコンテンツをスキャンして特定のアーティファクト(痕跡、指標)を探し、復元できそうなコンテンツがもっとないかを判定する関数群もあります。以下のセクションでは、コンテンツを静的に識別し、内容を明らかにするために使うさまざまな手法を順を追って説明します。
- 本スクリプトで扱うコンテンツの反転はもっとも単純な種類の1つですが、私の経験上、それほど一般的ではありません。この例では、一般的なPowerShellメソッドを反転させた文字列を検索し、その後コンテンツ全体を取得してそれも反転させ、元のコンテンツのコピーから作成した2つめのコンテンツ ストリームの末尾に追加します。
1 2 |
RAHC[+58]RAHC[+501]RAHC[((eCALpEr.)93]RAHC[]GNirtS[,'V3wfe'(eCALpEr.)).rEpLACe('efw3V',[StriNG][CHAR]39 ).rEpLACe(([CHAR]105+[CHAR]85+[CHAR |
- レビューアの目からコンテンツを隠蔽するもう1つの一般的な方法は、WindowsのStreamオブジェクトを利用するものです。Streamオブジェクトは事実上、コンテンツのエンコードに使用されるクラスです。この例でプロファイリング用スクリプトは、目に見えるコンテンツ内に呼び出しが存在するかどうかを識別し、ストリームの圧縮を試みます。デフォルトだとMicrosoftはgzipと同じ圧縮アルゴリズムを使用しますが、本スクリプトでは対応可能範囲を広げるために複数の異なる圧縮設定をブルートフォース方式で使います。
1 2 |
New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('Cy/KLEnV9cgvLlFQz0jNycnXUSjPL8pJUVQHAA=='),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd() Write-Host 'hello, world!' |
- 次はBase64です。これが現状最も基本的で汎用的なエンコーディング方式でしょう。ここではとくに凝ったことはしていません。特定のサイズ(現在は30バイト)を超えるBase64の正規表現パターンにマッチするコンテンツをすべて取得し、デコードし、元のコンテンツのコピーから作成した2つめのコンテンツ ストリームの末尾に追加します。
1 2 |
RAB5AG4AYQBtAGkAYwBBAHMAcwBlAG0AYgBsAHkA DynamicAssembly |
- 最後に処理するのがキーを使用してSecureStringが作成された場合にCBCモードでデフォルトAESを使用するMicrosoft SecureStringsの基本的復号です。本スクリプトは、これらSecureStringを復号する呼び出しを探し、対称暗号化キー、すべてのBase64コンテンツ、コンテンツの復号に必要なIV(初期化ベクトル)の特定を試みます。実際のインターネット上のサンプルでは、ここでの説明に使えるような小さなデータを見たことがないので、急遽かわりのサンプルを作りました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
> $SecureString = ConvertTo-SecureString "EXAMPLE" -AsPlainText -Force > $StandardString = ConvertFrom-SecureString $SecureString > $Key = (68,111,110,116,72,105,114,101,84,111,109,76,97,110,99,97,1 15,116,101,114,70,65,67,84) > $StandardString = ConvertFrom-SecureString $SecureString -Key $Key > $StandardString 76492d1116743f0423413b16050a5345MgB8AFAAZQBHAHoAeABvAG0AVAA 5AGkAQgA1AEEATABsAGoANABnAFgATABSAFEAPQA9AHwAMAA0ADQANQBiAD EANQA2ADIAYgA3AGQAMwBmADIAZgA1ADYAYgA5AGUAZgAwADAAMgAyADYAZ QAzAGMAMQAzAA== |
この例では、文字列「EXAMPLE」が$Keyに指定した24バイトによって暗号化され、Base64値が返されます。このBase64 blobの内部コンテンツ構造については公開済みドキュメントがないようですが、ここには暗号化された文字列とBase64でエンコードされたIVが含まれています。これらの値はパイプ ("|") で区切られているので、ここでは赤でIVを、青で暗号化されたデータを強調表示してみました。これらがプロファイリング用スクリプトの関数が復号する対象となります。
1 2 3 |
\xef\xae=\ xd9\xddu\xd7\xae\xf8\xdd\xfd8\xdb~5\xdd\xbdz\xd3\x9d\x1a\xe7~92|<span style="color: #ff0000;"><strong>PeGzxomT9iB5ALlj4gXLRQ==</strong></span>|<span style="color: #0000ff;"><strong>0445b1562b7d3f2f56b 9ef00226e3c13</strong></span> |
概要では、多くの悪意のあるスクリプトが一般的に使用することがわかったすべての手法にくわえ、隠蔽されたコンテンツを明らかにするために必要なさまざまな種類の難読化解除手法や正規化手法について説明しました。もちろん。すべてを網羅することを意図したものではありませんが、最初のステップとしては上出来でしょう。
既知のマルウェアファミリのプロファイリング
このプロファイリング用スクリプトは、可能なかぎりのコンテンツを表出させてから、識別を行う段階に入ります。まずは既知のマルウェアファミリとその亜種を探すところから始めます。これなら既知のIoCに基づいてすばやくファイルをスコアリングし、背後にある意図をごく短時間で確認できるからです。ここで主に使うのは正規表現パターンかキーワードセットです。それらを使って、Magic Unicorn、Social Engineer Toolkit (SET)、Veilなどの悪意のあるスクリプトを一意に特定していきます。現在のスクリプトでチェックしているマルウェア ファミリの全リストは以下のとおりです。このリストは、新たな形式がインターネット上で増えれば更新されることがあります。
- Magic Unicorn
- ShellCode Injector
- ICMP Shell
- SET
- PowerDump
- BashBunny
- Veil
- PowerWorm
- PowerShell Empire
- Powerfun
- Mimikatz
- Mimikittenz
- PowerSploit
- DynAmite
- Invoke-Obfuscation
- TXT C2
- Remote DLL
- Cobalt Strike
- Vdw0rm
- Emotet
- mateMiner
- DownAndExec
- Buckeye
- APT34
- MuddyWater
- Tennc Webshell
- PoshC2
- Posh-SecMod
- Invoke-TheHash
- Nishang
- Invoke-CradleCrafter
これらはすべて、前述の手動による分析やこれまでの研究からの派生してきたものです。レビュー中のスクリプトやスクリプト構造になんらかのパターンが浮かび上がってきた場合、それはそれらのファイルがフレームワークやスクリプトを使って生成された可能性が高いことを示しています。こうした指標があれば、プロファイルがうまくいくケースは多くなります。繰り返しになりますが、私のスクリプトは上のマルウェア ファミリすべてを網羅しているわけではありません。ですから、本スクリプトを使っていて、新しい亜種や新しいファミリを特定した場合は、スクリプトで対応できる範囲を拡大するため、それらを追加していくとよいでしょう。
PowerShellの振る舞いプロファイリング
では、PowerShellの振る舞いを実際にプロファイリングしていきましょう。振る舞いは、3つの異なるコンテキスト カテゴリに分類されます。1つめは、通常は悪意のあるスクリプトでのみ見られる振る舞い。2つめは、一般に良性のスクリプトと悪性のスクリプトの両方で見られる中立的な振る舞い。3つめは、一般に良性のスクリプトでのみ見られる振る舞いです。
これらの振る舞いすべてと、各振る舞いに対応するスコア(スクリプトから直接取得したもの) を以下に示します。これらがどのように機能するかについても簡単に説明します。
悪意のある振る舞い
- 'Code Injection': 10.0
- 'Key Logging': 3.0
- 'Screen Scraping': 2.0
- 'AppLocker Bypass': 2.0
- 'AMSI Bypass': 2.0
- 'Clear Logs': 2.0
- 'Coin Miner': 6.0
- 'Embedded File': 4.0
- 'Abnormal Size': 2.0
- 'Ransomware': 10.0
- 'DNS C2': 2.0
- 'Disabled Protections': 4.0
- 'Negative Context': 10.0
- 'Malicious Behavior Combo': 6.0
- 'Known Malware': 10.0
ここでは、悪意のあるスクリプトを6より上のスコア範囲におさめようとしていることを忘れないでください。スコアが高いほど判定の確度も高くなります。
これらの振る舞いはその大半が、シンプルなキーワードスキャンか、キーワードの組み合わせによってプロファイリングできます。では、簡単なものを例にとって、その振る舞いがPythonコード内でどのように定義されているかを見てみましょう。
1 2 3 4 5 |
behaviorCol["Disabled Protections"] = [["REG_DWORD", "DisableAntiSpyware"], ["REG_DWORD", "DisableAntiVirus"], ["REG_DWORD", "DisableScanOnRealtimeEnable"], ["REG_DWORD", "DisableBlockAtFirstSeen"], ] |
あるスクリプトに対して「『Disabled Protections (保護の無効化)』を狙った振る舞いがある」というフラグを立てる場合、そのコンテンツには、"REG_DWORD"の4つのバリエーションのうちのいずれか1つと、悪意のあるスクリプトが一般的なWindowsの保護メカニズムであるAntiSpywareやAntiVirusを無効化しようとしたときに観測されるレジストリ キーが含まれていなければなりません。
次に、上とは別の『Key Logging (キーロギング)』を狙う簡単なサンプルを見てみると、ここではさまざまな組み合わせのキーワードがあることがわかります。これらのキーワードが同時に見つかった場合、それは通常、キーロギングの活動を示しています。
1 2 3 4 5 |
behaviorCol["Key Logging"] = [ ["GetAsyncKeyState", "Windows.Forms.Keys"], ["LShiftKey", "RShiftKey", "LControlKey", "RControlKey"], ] |
「Disabled Protections」と「Key Logging」のスコアはそれぞれ4.0と3.0なので、悪意のある活動を示すしきい値である6.0を大きく下回っています。このようにした理由は、ある振る舞いの圧倒的多数が悪意のあるスクリプトでしか見られないという場合でも、ときとしてそれらが良性のスクリプトでも使用されることがあるためです。そこで私は、別の振る舞いをさらに追加することによってコンテキストを与え、スコアをしきい値より上に押し上げるようにしたいと考えたのです。これとは対照的に「Ransomware(ランサムウェア)」や「Known Malware (既知のマルウェア)」に分類される振る舞いであれば、プロファイリング用スクリプトはただちにしきい値の6.0を超えたスコアを与えます。
ここで紹介できるさらに複雑な振る舞いは「Code Injection (コードインジェクション)」でしょう。コードインジェクションとは、ある特定の呼び出しパターンに従ってメモリの1セグメントを切り出し、そのセグメントにシェルコードを注入し、最終的にはそのメモリ内のシェルコードに実行を移す、という手法です。問題は、これら各段階でそれぞれの機能を実現する方法が非常に多いことです。そこで、このプロファイリング用スクリプトでは、単独で1,300種類の方法をカバーし、各段階で1つずつキーワードをチェックし、ひとつ前の段階のキーワードセット内に該当するキーワードが見つかった場合にのみ、次のキーワードセットに進むようにしています。これにより、キーワードを追加するたびにバリエーションが大幅に増加しても、分析時間を短く抑えることができます。大規模なプロファイリングにおいては速度も品質同様に重要ですから、プロファイリング用スクリプトの多くは、可能な限り実行時間を短縮するよう設計されています。
上記の振る舞いリストの最後の方に「Malicious Behavior Combo (悪意のある振る舞いの組み合わせ)」というものがあるのにお気づきでしょうか。この振る舞いカテゴリは、ある悪意のあるスクリプトが、目標のしきい値を超えるスコアを生成するほど十分な振る舞い情報を示さない場合、その振る舞い自体をコンテキスト修飾子として加えることによりスコアの引き上げをねらったものです。プロファイリング用スクリプトは、PowerShellスクリプト全体の振る舞いを調べてから、「Malicious Behavior Combo」を独立したひとつの振る舞いとして使用します。
この振る舞いの組み合わせは、ベースラインとして使用するスクリプト群の以前説明した「グラウンドトゥルース (正しい答え)」と照合され、良性のスクリプトに悪影響を与えないかどうかを検証しています。組み合わせによる影響は注意深く監視する必要があります。データが多くなればなるほど、誤ってマッチしてしまう良性のスクリプトが出てくるはずだからです。ただし、そうした例は、メタデータを使ってスコアリングに影響を与えるよいサンプルになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
behaviorCombos = [ ["Downloader", "One Liner", "Variable Extension"], ["Downloader", "Script Execution", "Crypto", "Enumeration"], ["Downloader", "Script Execution", "Persistence", "Enumeration"], ["Downloader", "Script Execution", "Starts Process", "Enumeration"], ["Script Execution", "One Liner", "Variable Extension"], ['Script Execution', 'Starts Process', 'Downloader', 'One Liner'], ['Script Execution', 'Downloader', 'Custom Web Fields'], ["Script Execution", "Hidden Window", "Downloader"], ['Script Execution', 'Crypto', 'Obfuscation'], ["Hidden Window", "Persistence", "Downloader"], ] |
中立的な振る舞い
- 'Downloader': 1.5
- 'Starts Process': 1.5
- 'Script Execution': 1.5
- 'Compression': 1.5
- 'Hidden Window': 0.5
- 'Custom Web Fields': 1.0
- 'Persistence': 1.0
- 'Sleeps': 0.5
- 'Uninstalls Apps': 0.5
- 'Obfuscation': 1.0
- 'Crypto': 2.0
- 'Enumeration': 0.5
- 'Registry': 0.5
- 'Sends Data': 1.0
- 'Byte Usage': 1.0
- 'SysInternals': 1.5
- 'One Liner': 2.0
- 'Variable Extension': 2.0
中立的な振る舞いは、その名が示すとおり良くも悪くもない振る舞いのことです。これについても前述の悪意のある振る舞いと似た方法で処理しますが、「Obfuscation (難読化)」、「One Liner (ワンライナー)」、「Variable Extension (変数展開)」などのメタ的な動作については、追加で説明しておきたいと思います。これらへのアプローチは、いままでのキーワードによるアプローチとは若干異なるのです。かいつまんで言うと、このメタ的な動作とは、特定の機能や機能を表すのではなく、あるPowerShellスクリプトに見られる特性を指しています。
- One Liner: 名前からも自明ですが、1行で完結するスクリプトのことです。悪意のあるスクリプトや、ダウンロードだけを行う非常に単純なPowerShellスクリプトの場合、その多くが1行に詰め込まれています。通常、良性のスクリプトはより構造化された形態をもち、改行も複数含まれています。
- Obfuscation: プロファイル用スクリプトは文字出現頻度、シンボル使用数、変数宣言数などのさまざまな属性内容を処理しますが、その処理が行われるのがこのObfuscation(難読化)です。これらの属性は、さまざまな方法でカウントされ、悪意のあるスクリプトに観測される特性がそこから強調されます。
- 文字頻度分析とは、良悪両方のスクリプトのサンプルセット全体で観測された、文字使用量の標準偏差を反映したものです。たとえばあるスクリプトで「w」という文字が500回以上使用されているとか、コロン「:」が100回以上使用されている、などといった特性を見つけ出そうとします。このカテゴリに分類されているのは、13種類の個別文字と3組の対になる文字("["と"]"など)で、対象となるスクリプトの行数が50行未満の場合の分析に使われます。これは通常、これらの文字が密集していることを示しています。
- シンボル使用数は、以下のような変数宣言でよく使用されている特定の種類の難読化を指しています。
1 |
${/=\__/==\_/\/==\_} = [AppDomain]::CurrentDomain |
-
- 変数宣言数は、PowerShellやJavaScriptにおいて、一意な変数宣言数が40個を超えているかどうかを確認します。
3. Variable Extension: PowerShellでよくある難読化手法としてもう1つあげられるのが、変数におけるワイルドカード("*")機能の利用です。プロファイリング用スクリプトは、変数の取得・設定を行うさまざまなコマンド数をカウントしてから、ASCIIコード文字で囲まれたワイルドカード数をカウントします。それらのカウントが一定数を超えている場合、難読化の種類としてこのVariable Extensionのラベルが付与されます。この機能を例で示すと、たとえば次のコードで"ExecutionContext"を取得することができます。このようなコードをつなぎ合わせて別のコマンドを生成することはよくあります。
1 2 |
Get-Item Variable:*xec*t ExecutionContext |
中立的振る舞いのスコアはかなり低めに設定されているので、複数の振る舞いフラグが設定されただけでは、通常は悪意のあるスクリプト判定のしきい値に設定した値を超えるほどにはなりません。
良性の振る舞い
- 'Script Logging': -1.0
- 'License': -2.0
- 'Function Body': -2.0
- 'Positive Context': -3.0
悪意のあるスクリプトに良性の振る舞いが現れることはめったにありません。そこで、良性の振る舞いを全体のスコア合計から差し引くことで、コンテキストに影響を与え、精度を高めるようにしています。たとえば、典型的な悪意のあるスクリプトの場合、ロギングやライセンス、関数プリアンブルは使用されません。ここでのキーワードは「典型的な」です。攻撃者は、Github経由でPowerShellの攻撃用フレームワークからスクリプトをダウンロード後、それらをクリーンアップすることを気にかけないことがあります。そのようなケースではほぼ常に、難読化以外の振る舞いが大量に含まれており、それがスコア低下を補います。
最後の「Positive Context (ポジティブなコンテンツ)」は、良性のスクリプトであるにもかかわらず多くの振る舞いカテゴリに抵触してしまい、悪意のあるスクリプトのしきい値を超えてしまうような場合に、意図的にスコアを低下させるために使用するものです。これはエンドポイントやブートストラップシステム上で多岐にわたる管理機能を実行するエンタープライズ向け管理用スクリプトの処理によく見られるケースです。
結論
シリーズ第2回目となる本稿では、私がこれまで観測してきた一般的なデータ隠蔽用のPowerShellテクニックについて解説しました。また、そうしたテクニックを正規化によってクリーンアップし、さまざまな方法で難読化されたコンテンツを復元する方法についても学びました。これらは、振る舞いプロファイリングのベースとなるコンテンツを今後さらに解析していくうえで、事前に行っておくべき重要な処理です。
本シリーズ3回目となる最終稿では、これまでに説明した内容すべてがスクリプトをプロファイルするさいどのように連携するかについて詳しく見ていきます。また、PowerShellスクリプトを静的に分析して得られた観測内容を共有したいと思います。