VBAとややこしい兄弟関係にあるExcel 4.0のマクロ(XLM)の歴史は古く、いくつかの興味深い攻撃テクニックに利用できるものとして注目されてきました。OutflankのStan HegtとPieter Ceelenが初めてハッキングに使用して以降、サイバーリーズンでも、一風変わった小規模のラテラルムーブメントのテクニックにこの機能を試したことがありますが、今やこのマクロはシェルコードランナーとして武器化するまでに大きな進化を遂げています。

サイバーリーズンでは過去において、Device Guard Bypassとしてこの機能を使用したことがあり、また、ここ最近では、Stan Hegtがシェルコードのアプローチとラテラルムーブメントのアプローチを組み合わせ、DCOMからExcelのリモート機能でRawのシェルコードを実行することに成功しています。これまでのところ、一部の機能は32ビットバージョンのExcelのみで可能でした。これは、Excel 4.0のマクロシステムに存在するいくつかの制限事項によるものです。

このタイプの攻撃は、悪意のあるVBAコードによる攻撃ほど広まってはいませんが、それでも、2つの点で大きな効果を発揮します。まず、この攻撃は分析が難しく、アンチウイルスソリューションの多くがなかなか検知できずにいます。そしてさらに興味深いことに、きわめて古いタイプのマクロであるにもかかわらず、Excel 4.0のマクロは最新バージョンのMicrosoft Officeでもサポートされているのです。

この調査・研究レポートでは、64ビットのシェルコードをExcel 4.0のマクロで実行する方法の概要を説明します。また、いくつかの制限事項により、32ビットのシェルコードの実行テクニックを変更加えずにそのまま使用することはできませんが、これらの制限事項についても説明します。

64ビットのOfficeにこのテクニックを拡張して適用した結果、この手法は、これまで想定していたよりもずっと広範な影響をもたらすことが判明しており、その点で特に興味深い手法であると言えます。デフォルトでインストールするOfficeのバージョンを64ビットのOfficeにするとMicrosoftは最近決定しましたが、これにより今後は、64ビットのOfficeを利用した攻撃が拡大することになるでしょう。

32ビットのEXCELでシェルコードを実行する

Excel 4.0のマクロについて知るには、まず、CALL関数とREGISTER関数を理解せねばなりません。これらの関数を使用すると、任意のDLLにエクスポートした関数を実行することができます。この調査・研究レポートでは、概念実証(POC)にCALL関数を使用しているため、CALL関数に焦点を当てています。

CALL関数を使用するには、ExecuteExcel4Macroのメソッドを呼び出します。

$excel.ExecuteExcel4Macro(‘CALL(“advpack”, “LaunchINFSectionA”, “JJJFJ”, 0, 0, “c:\\temp\\test.inf, DefauoltInstall_SingleUser, 1”,0)’)
ExecuteExcel4MacroのメソッドでCALL関数を使用する

CALL関数は3つの引数を受け取り、これらはどれも必須です。1つ目の引数は、関数のインポート先となるライブラリの名前であり、2つ目の引数は、関数自体の名前です。そして3つ目の引数は、インポートされる関数のシグネチャを表す文字列になります。インポートする関数についてExcelが唯一知っているのは、GetProcAddress経由で受け取るアドレスだけです。つまり、このマクロでは、引数を記述し、インポートのタイプを返す必要があるわけです。

ExecuteExcel4Macroの文字列の引数「JJJFJ」のうち、最初の1文字は戻り値を、残りの文字は引数を表します。個々の文字は、Excel 4.0のマクロが処理できるデータのタイプに対応しています。たとえば、「J」は、署名された4バイトの整数を意味し、一方「F」は、nullで終了する文字列を表します。

32ビットバージョンでは、サポートされていないデータタイプを、サポートされている同じサイズのデータタイプと容易に置き換えられます。たとえば、ポインターは、4バイトの「J」タイプとして扱うことができます。この結果、下記のような引数が4バイト以下の関数を使ってシェルコードを実行することが可能になるのです。

引数が4バイト以下の関数
  • LPVOID VirtualAlloc(LPVOID lpAddress、SIZE_T dwSize、DWORD flAllocationType、DWORD flProtect)
  • BOOL WriteProcessMemory(HANDLE hProcess、LPVOID lpBaseAddress、LPCVOID lpBuffer、SIZE_T nSize、SIZE_T *lpNumberOfBytesWritten)
  • HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes、SIZE_T dwStackSize、LPTHREAD_START_ROUTINE lpStartAddress、__drv_aliasesMem LPVOID lpParameter、DWORD dwCreationFlags、LPDWORD lpThreadId)

x64に対応できるようにする

このマクロの機能は64ビットのExcelにも存在しますが、同じアプローチでシェルコードランナーを実装しようとすると、すぐに1つの問題にぶつかります。当然のことながら、64ビットのアプリケーションのポインターのサイズは64ビットになります。利用できるデータタイプは同じままであり、これは、ネイティブの8バイトの整数タイプが存在しないことを意味します。いずれかの浮動のポイントタイプを利用する場合は、XMMレジスターを使用することになります。つまり、x64の呼び出し規則に従えば、関数が期待する引数は、rcx、rdx、r8、r9などになるというわけです。

ただし、リファレンスが渡す文字列のデータタイプは機能するようです。このマクロシステムは、少なくとも一部の8バイトのポインターを処理する方法を知っていますが、8バイトの値を正確に受け渡しすることは不可能であるため、これだけでは、問題の直接の解決にはなりません。

一方、ポインターが0x0000001’00000000よりも小さい場合は、ポインターを4バイトだけで表すことができるので、問題は生じなくなります。そして、スタックではなくレジスターを通じて渡される関数の引数のうち最初の4つには少なくとも同じことが当てはまります。

レジスターの入力時に、これらの引数はゼロ拡張され、0x50000000は0x00000000’50000000になり、32ビットの値として使用する場合、上位のビットは破棄されます。

このような事実があることから、VirtualAllocのlpAddressパラメータを使用し、0x00000000から0xFFFFFFFFの範囲でメモリが割り当てられるように指定することができました。この範囲は、利用可能なデータタイプ通じて提供が可能なメモリの範囲です。このPOCでは、アドレスの候補として0x50000000(1342177280)を選択し、64ビットのExcelを介したVirtualAllocの実行を試みました。


64ビットのExcel経由でVirtualAllocを実行する

この手法では望んでいたとおりの結果が得られ、戻り値(新規で割り当てられるバッファを示すポインター)はlpAddressパラメータでリクエストしたものと同じになりました。実証は成功です。

メモリに空きがないときには、固有のアドレスを割り当てられないことがあります。ASLRなどの関数では、これが発生する可能性があります。この場合は、32ビットで表すことのできる別のアドレスを試せばそれで済みます。

上記と同じ手法を用いてWriteProcessMemoryをコールすると、プロセスがすぐにクラッシュします。スタックは破損し、関数がスタックベースのパラメータの1つを使用としたときにアクセス違反になります。

CALLインポートのためのパラメータを準備する64ビットバージョンの関数は、64ビットの値を上手く処理することができません。それどころか、スタックベースのパラメータを使用すると、次のCALL関数のスタックに悪影響が生じます。この問題を回避するために、memsetに使用するWriteProcessMemoryを切り替えました。これにより、レジスターを通じて提供される3つの引数だけを使用し、スタックの破損を無視するようにしています。この結果、CreateThreadのコールで、シェルコードの実行が開始されます。


CreateThread をコールして、シェルコードの実行を開始

Invoke-Excel4DCOMについては、x64対応のアップデートがすでに行われています。

処理速度の最適化とその他の問題

リモートロケーションシェルコードによる現状のインジェクションテクニックで問題となるのは、パフォーマンスです。ペイロードを1バイトずつリモートマシンに書き込むような方法では処理に相当な時間がかかり、DCOMプロトコルのオーバーヘッドを考慮した場合、余計な時間がかかってしまうことになります。

一度に複数バイトの書き込みができるようにするための解決策としては、Kernel32!RtlCopyMemory関数の利用が考えられます。この関数は基本的にはmemcpy用のラッパーであり、そのパラメータは3つだけです(すでに述べたように、WriteProcessMemoryを使用すると、64ビットのExcelではクラッシュが生じます)。

書き込むバイトを*Sourceパラメータとして表す文字列を用いてRtlCopyMemoryを何度かコールすれば、一度に10バイトの書き込みができます。


RtlCopyMemoryを使用して一度に10バイトの書き込みを行う

これにより、シェルコードを標的のバッファにすばやく書き込めるようになるのです。ただし、この方法でも、ペイロードを実行する前にプロセスがクラッシュするでしょう。CreateThreadのスタックパラメータ(具体的にはlpThreadId)が破損してしまうからです。

こういったわけで、memsetを使用するアプローチでスタックパラメータが破損せずCreateThreadが正常に機能したとしてもそれは偶然に過ぎないと確信するに至ったのです。
この問題を解決するためのアプローチとしては、次の2つが考えられます。

      リバースエンジニアリングを用いてExcel 4.0のマクロのCALL機能を解析し、この機能がx64のExcelでスタックベースのパラメータをどのように準備しているのか(どのようにその処理に失敗しているのか)を把握する。そしてその一方で、問題の根本原因に対処する方法を探す。
      バグそのものに対処するのは潔く諦め、スタックベースの引数を使用しない別の関数をCreateThreadのコールに使用する。

この調査・研究では、2番目の方法を選択することにしました。1つ目の方法では、CALLの機能や引数の処理方法をリバースエンジニアリングで解析することになりますが、その作業には相当な困難が予想されました。また、適切なソリューションがすぐに見つかる見込みもありませんでした。

いくつかの異なる実行プリミティブを評価しましたが、最大で4つのパラメータを持つ新しいスレッドを作成できシンプルでエクスポートが可能な関数を見つけることはできませんでした。そこで、APCメカニズムを使用してプロセスの実行操作を行うことにしました。

APC(非同期プロシージャコール)をスレッドにキューイングすると、スレッドがアラートを発生できる状態になった時点でスレッドのコンテキストに応じ、スレッド実行の呼び出し元がコードを提供します。QueueUserAPCをこのような用途で使用する場合、必要な引数は3つに限られ、QueueUserAPCがスタック上のパラメータを探すことはありません。シェルコードのアドレスを格納したAPCをこの関数を使用して最新のスレッドにキューイングしました。ここで言う最新のスレッドとは、CALLマクロを処理するスレッドを意味します。


QueueUserAPCを使用し、スレッドがアラートを発生できる状態になった時点でスレッドにコンテキストを付与する

簡単なサニティチェックを実施した結果、マクロの処理を担うスレッドはExcelの単一のインスタンスにおいていずれの場合も同じスレッドであることがわかりました。


サニティチェックの結果:マクロを処理するスレッドがどの場合も同じスレッドであることがわかる

NtTestAlertなどの関数を使用すれば、最新のスレッドのAPCキューをフラッシュおよび実行することや、スレッドを正確に指定してシェルコードを実行することが可能です。


NtTestAlert により、スレッドを正確に指定してシェルコードを実行

そして、x64に対応するようにシェルコードランナーを書き換え、上記のような最適化を施し、Invoke-ExShellcodeとして誰もが利用できるようにしました。

まとめ

オリジナルの手法もここで紹介した手法も、そのベースとなっているのはDCOMによるラテラルムーブメントの攻撃です。この手法やその他類似の手法に対処するための対策については、過去の記事に詳しい情報がありますので、そちらをご覧ください。

DCOMをベースとするさまざまな攻撃についてご説明しています。DCOMでは、Excelのオブジェクトのような危険性のあるオブジェクトにアクセスできてしまいます。これらオブジェクトへのdcomcnfgなどによる、DCOMを通じたアクセスを禁止すれば、意図しない結果が生じるリスクを大きく減らすことができます。

それゆえ、ポリシーを用いてアプリケーションのアクセスに制限をかけ、必要に応じてホワイトリストの登録設定を厳しく実施するようお勧めします。

ここに挙げた攻撃は、VBAコード以外の悪意のあるマクロを64ビット環境で使用するという新しいアプローチであり、興味深いものであると言えます。この攻撃では、Excel 4.0のマクロを通じて64ビットのシェルコードを実行できるようになっています。攻撃者はこの手法を利用して、標的のコンピューターへのアクセスやデータの窃取、ラテラルムーブメントの実行などが可能です。

ホワイトペーパー「すべての組織が狙われている」

企業、組織がどんなにセキュリティを強固にしてもハッカーが悪用できる脆弱性は必ず存在します。侵入されることが避けられないことを受け入れ、新たな対策を立てる必要があります。本書で、なぜ避けられないのか、どのように対処するのかをご覧ください。
https://www.cybereason.co.jp/product-documents/input/?post_id=606

ホワイトペーパー「すべての組織が狙われている」