エンジニアリング

メモリーシグネチャでCobalt Strikeを検知する

Elasticセキュリティチームは脅威検知という課題に対し、複数の手法を用いるアプローチを採用しています。従来Elasticは、機械学習モデルとデータの振る舞いに力点を置いてきました。この2つが、未知のマルウェアも検知できるパワフルな手法であるためです。またこれまで、Elasticはシグネチャについて「非常に簡単に回避できる」ことが懸念材料と捉えてきました。しかし、この「回避の簡単さ」は考慮すべき要素の1つに過ぎないことから、その評価をあらためています。検知技術の効果を測定する上では、パフォーマンスと誤検知率も重要です。

シグネチャで、未知のマルウェアを検知することはできません。しかし、誤検知率がほぼ0であることや、ラベルの関連付けがアラートの優先順位付けに役立つというメリットがあります。たとえば“潜在的に望ましくないアドウェアの一種がある”というケースなどと違い、TrickBotランサムウェアやREvilランサムウェアのアラートが発生しているケースでは、より即時的なアクションが必要です。万が一、シグネチャで既知のマルウェアの半分しか検知できなかったとしても、多層的な保護を講じていれば総合的にはなお十分なメリットがあり、大勝利を収めたということができます。もちろん現実のシナリオでは、もっと良い結果を出せるでしょう。

長期的なバリューをもたらすシグネチャを作成する上で障害となるのが、広範に利用されるパッカーと、使い捨てマルウェアローダーの存在です。この2つのコンポーネントは日々急速に進化してシグネチャ検知を回避します。しかし、マルウェアのペイロードは最終的には復号化され、メモリーで実行されることに変わりはありません。

このパッカーとローダーの問題に対処する上で注目したいのが、シグネチャ回避戦略をインメモリーのコンテンツで展開する、という手法です。この手法を用いると、従来数日だったシグネチャの鮮度を数か月ほどに延長して維持できます。本ブログ記事はサンプルにCobalt Strikeを用いて、インメモリーシグネチャを活用する手法を説明します。 

Cobalt Strikeのシグネチャを作成する

Cobalt Strikeは、レッドチームオペレーションと攻撃シミュレーションによく使われるフレームワークです。使いやすさや安定性、ステルス機能も人気の理由の一部と考えられますが、実はCobalt Strikeは極悪非道な意図を持つ悪意のアクターに愛用されているツールでもあります。Cobalt Strikeのエンドポイントペイロード、Beaconの検知には多種多様なテクニックが使われています。アンバックされたスレッドを探すものや、最近は内蔵の名前付きパイプをチェックするものなどがあります。しかしBeaconはコンフィギュアビリティにすぐれ、ほとんどのパブリックな検知戦略に回避方法があります。そこで今回は、代替的な検知戦略としてメモリーシグネチャを活用します。

一般的に、Beaconはメモリーに再帰的にロードされ、ストレートにシグネチャ化可能な形でディスクに関与することはありません。さらに、攻撃者はBeaconに多様なインメモリー難読化オプションを設定し、そのペイロードを隠すことができます。たとえばobfuscate-and-sleep(難読化してスリープ)というオプションは、コールバックの間でBeaconのペイロードの一部を隠し、シグネチャベースのメモリースキャンをピンポイントで回避します。シグネチャの開発にあたってはこのオプションを考慮する必要がありますが、このように高度なステルス機能を持つBeaconについても、簡単にシグネチャを作成できます。

作成をはじめる

はじめに、最近のリリースからsleep_maskオプションが有効なものと、無効なものを含むいくつかのBeaconペイロードを取得します(ハッシュはリファレンスセクションに記載しています)。まずsleep_maskを無効化したサンプルで作業をはじめましょう。破壊後にアンバックされたリージョンからSleepExをコールしたスレッドを見つけ、Process Hackerでメモリー内のBeaconを特定します。

アンバックされたリージョンで、関連するメモリーのリージョンを分析のためにディスクに保存します。

最も簡単なのは、このリージョンに特有の文字列をピックアップして、シグネチャとして使うやり方です。以下に、シグネチャ作成の標準的なツール、yaraを使ったシグネチャの記述を実演します。

rule cobaltstrike_beacon_strings
{
meta:
    author = "Elastic"
    description = "Identifies strings used in Cobalt Strike Beacon DLL."
strings:
    $a = "%02d/%02d/%02d %02d:%02d:%02d"
    $b = "Started service %s on %s"
    $c = "%s as %s\\%s: %d"
condition:
    2 of them
}

これがシグネチャの土台部分になります。さらにsleep_maskが有効化されたサンプルを確認して、改良を加えることができます。メモリー内で通常はMZ/PEヘッダーが見られる部分を確認すると、難読化されていることがわかります。

パッと見ただけで、nullバイトがあるべき場所に、多数の繰り返しのバイト(ここでは0x80)があることに気づきます。この繰り返しバイトは、Beaconが単純なシングルバイトのXOR難読化を使用した痕跡かもしれません。この仮説は、CyberChefを使って確認できます。

上の画像の通り、デコード後に“This program cannot be run in DOS mode”(このプログラムはDOSモードで実行できません)という文字列が出現し、先ほどの仮説が証明されました。シングルバイトのXORは古典的な手口であり、実はyaraもxor修飾子のネイティブ検知をサポートしています。

rule cobaltstrike_beacon_xor_strings
{
meta:
    author = "Elastic"
    description = "Identifies XOR'd strings used in Cobalt Strike Beacon DLL."
strings:
    $a = "%02d/%02d/%02d %02d:%02d:%02d" xor(0x01-0xff)
    $b = "Started service %s on %s" xor(0x01-0xff)
    $c = "%s as %s\\%s: %d" xor(0x01-0xff)
condition:
    2 of them
}

スキャン中にPIDを提供して、作成したyaraルールの検知が動作していることを確認できます。

残念ながら、ここで終わりではありません。最新バージョンのBeacon(本記事の執筆時点では4.2です)のサンプルでこのシグネチャをテストしたところ、難読化ルーチンが改善されていることがわかりました。このルーチンは、前出のコールスタックに沿って特定することができます。以下のIDA Proスニペットに示すように、このBeaconは現在13バイトのXORを使用しています。


幸いにも、Beaconのobfuscate-and-sleepオプションは文字列とデータだけを難読化するもので、シグネチャの作成に必要なコードセクション全体はそのまま残っています。問題は、このコードセクションのどの関数を使ってシグネチャを開発すればよいか、という点ですが、それこそがこの記事の存在理由でもあります。ひとまず、この難読化の解除ルーチンのシグネチャを作成し、うまく動作することを確認しましょう。

rule cobaltstrike_beacon_4_2_decrypt
{
meta:
    author = "Elastic"
    description = "Identifies deobfuscation routine used in Cobalt Strike Beacon DLL version 4.2."
strings:
    $a_x64 = {4C 8B 53 08 45 8B 0A 45 8B 5A 04 4D 8D 52 08 45 85 C9 75 05 45 85 DB 74 33 45 3B CB 73 E6 49 8B F9 4C 8B 03}
    $a_x86 = {8B 46 04 8B 08 8B 50 04 83 C0 08 89 55 08 89 45 0C 85 C9 75 04 85 D2 74 23 3B CA 73 E6 8B 06 8D 3C 08 33 D2}
condition:
     any of them
}

Beaconがステルススリープ状態であっても検知できることを(32ビットと64ビットの両方のバリアントで)検証することができました。

より堅牢な検知を構築する目的で、システム(またはエンタープライズ全体)の全プロセスを定期的にスキャンすることもできます。これは、次のpowershellワンライナーで実行できます。

powershell -command "Get-Process | ForEach-Object {c:\yara64.exe my_rules.yar $_.ID}"

まとめ

ときに過小評価されるシグネチャベースの検知は、特にインメモリースキャニングの手法を検討することで、価値ある検知戦略となります。この記事の手順では、設定やステルス機能の有無にかかわらず、わずかな数のシグネチャでCobalt Strikeを検知することができ、有効誤検知率0という結果を出すことができました。

リファレンスハッシュ

7d2c09a06d731a56bca7af2f5d3badef53624f025d77ababe6a14be28540a17a
277c2a0a18d7dc04993b6dc7ce873a086ab267391a9acbbc4a140e9c4658372a
A0788b85266fedd64dab834cb605a31b81fd11a3439dc3a6370bb34e512220e2
2db56e74f43b1a826beff9b577933135791ee44d8e66fa111b9b2af32948235c
3d65d80b1eb8626cf327c046db0c20ba4ed1b588b8c2f1286bc09b8f4da204f2

Elasticセキュリティについて

まだ導入されていない方は、Elastic Agentのパワフルな保護、検知、対応機能をぜひご体感ください。無料の14日間トライアル(クレジットカード情報の入力は不要です)で使いはじめることも、オンプレミスのデプロイ向けに無料でElasticプロダクトをダウンロードしてお使いいただくこともできます。成功を導く準備の一環として、クイックスタートトレーニングもぜひご活用ください。