本サイトは、快適にご利用いただくためにクッキー(Cookie)を使用しております。
Cookieの使用に同意いただける場合は「同意する」ボタンを押してください。
なお本サイトのCookie使用については、「個人情報保護方針」をご覧ください。
はじめに
Contiランサムウェアは2020年5月に初めて確認され、現在全世界で多くの被害を出している標的型ランサムウェアであり、近年の流行に沿った二重脅迫を行う攻撃グループが用いるランサムウェアです。つまり、暗号化したファイルを復号するための身代金に関わる脅迫と、身代金の支払いに従わなかった場合にデータを流出させると脅し実際に徐々に公開するという二重の脅迫の手口と共に使用されます。
被害組織から盗み取った情報を公開する為に用意された攻撃グループのサイトをリークサイトと呼びますが、以下はContiランサムウェアのリークサイトのトップ画面であり、Contiランサムウェアの攻撃を受けた被害組織の情報が複数ページに渡り多数掲載されている状況が確認できます。
現在全世界には同様に専用のリークサイトを持つランサムウェア攻撃グループが複数存在しますが、弊社が把握しているそれら約20種類程度の攻撃グループのリークサイトを一通り調査し、各攻撃グループにおける被害企業数(攻撃グループのリークサイトで公開されている組織数)の割合を示したものが以下のグラフです(2021年4月MBSD調べ)。
Contiランサムウェアの攻撃グループが全体の25%を占める結果となり、公開されている中では現在活動中の全てのランサムウェア攻撃グループの中で最も活発かつ被害数が多いと推測され、2021年以降だけでも、ひと月あたり平均30組織を上回る数の被害が継続して毎月確認されています。
Conti攻撃グループは顧客から盗み取ったデータの中からサイバー保険の契約書を見つけ出し、保険の補償額と照らし合わせることで支払い能力を指摘し交渉してくるような交渉術も持ち合わせており、以下の図の数字にもその洗練された能力の高さが現れています。
そのため、今回は現時点において最大のランサムウェア脅威の一つといえるContiランサムウェアに着目し詳細解析した結果を公開することにしました。
Contiランサムウェア本体に関する解析記事は既に世界各国のベンダー等からいくつか出てはいますが、それらを”ランサムウェア本体の理解”という一つの限られた視点で見た場合、挙動の一部しか記載されていなかったり、処理全体が把握できなかったりする記事が多くを占めていると感じました。
そのため、今回は世界で最も丁寧で詳細なContiランサムウェアの解析記事となることを心がけました。
また、今回併せてContiランサムウェアが解析妨害する処理の流れを明らかにする専用の自動解析スクリプトも新たに作成しGitHubで公開しました。(詳細は本文中程をご参照ください)
通常であれば解説を省略するような内容についても可能な限り詳細に解説していますので冗長な部分はありますが、本記事がContiランサムウェア本体の挙動の全貌を理解する一助になれば幸いです。
Contiランサムウェアの全体挙動と概要
Contiランサムウェアは出現以降、速いペースでバージョンアップを行っており、本執筆時点の最新バージョンはV3(バージョン3)となっています。そのため、本記事もV3のContiランサムウェアを対象に選定し解析を行いました。
まずはざっと全体を把握いただけるように、Contiランサムウェアの全体挙動の概要を一枚の図にしたものが以下の図です。ファイルレスとなるReflective PE Injection(詳細は後述)を複数使用した解析検知妨害や、動作中に使用する全ての文字列やAPIに暗号化を施すなど、一般的な他のランサムウェアと比較して非常に手の混んでいる作りとなっています。
以降では、これらの挙動一つ一つについて詳細に解説していきます。
(本記事はとてもボリュームが多いため、読み進める中でどこの解説であるかを見失った際は、この図に戻り一度俯瞰してみてください)
表層解析
まずは表面的な構造から見ていきましょう。
ContiランサムウェアはEXEファイルであり、リソースセクションに以下のようなダイアログを持っていますが、これらはダミーであり実行しても使用されません。こうした使用しないリソースを含むものはたまに見られますが、分析された際に正常な実行ファイルであると偽装する意図があるものと考えられます。
図 4リソースセクションに埋め込まれた使用されないダイアログボックス
耐自動解析
Contiランサムウェアは実行されるとすぐにWindowsのメモ帳である「C:\Windows\notepad.exe」の属性変更を試みます。このシステムファイルへの属性変更処理は通常のWindows環境であれば常に失敗する処理となります。
この後の処理の分岐としては、属性変更処理が"成功した場合"のみ自身を終了し、"失敗した場合"のみ自身を継続する処理に遷移します。
つまり、「呼び出した関数が失敗すると動く」という不可思議なロジックになっており、そもそも失敗する前提で作られていることがわかります。
マルウェアの自動解析システムなどでは、マルウェアの処理の内部に何らかの関数の呼び出しがあり、その関数の成功有無によって変化する分岐が存在した場合、関数の結果が成功する遷移へ処理を強制変更させるというケースがありうるため、耐自動解析としてあえて正常な環境では必ず失敗するはずの分岐を挙動の冒頭に入れた可能性が考えられます。
再びEXEファイルの構造に話を戻しましょう。
最初に実行されるEXEファイルの表面的な構造と領域の比率を示したものが以下の図です。
以下の図からリソースセクション内に [RT_HTML->7765]という領域が存在することがわかりますが、214KB であるEXEファイル全体のサイズのうち、[RT_HTML->7765]のデータサイズは200KBと、EXEファイルにおけるサイズの大部分を占めていることがわかります(下図)。
図 6 ContiランサムウェアのEXEファイルのPE構造
では上記リソースセクション内の [RT_HTML->7765]という領域には何が含まれているのでしょうか。
[RT_HTML->7765]の領域をバイナリデータで表示させたものが以下の図です。[RT_HTML->7765]には判別不能な暗号化されたバイナリデータが格納されていることがわかります(下図)。
動き出したContiランサムウェアは、該当の [RT_HTML->7765]というリソース領域を指定し、新しく確保したメモリ領域(以降では、メモリ領域[α]と呼ぶ)にコピーします(以下図)。
この際、一般にあまり多くは使用されることのないネイティブAPIであるLdrFindResource_UとLdrAccessResourceを用いてリソースセクションから[RT_HTML->7765]を取り出します。
VirtualAlloc関数でメモリ領域[α]を確保する際は、コードとして実行可能なアクセス権である実行権を持つ状態で作成します。
以下は実際のメモリ領域[α]に展開されたデータとリソースセクションの[RT_HTML->7765]を比較した様子ですが、同じバイナリデータがそのままメモリ上にコピーされていることがわかります。
続いてContiランサムウェアは、メモリ領域[α]に展開した該当のデータを復号する作業に入ります。その際、"0x3D"という1バイトの値と以下のランダムな文字列を用いて復号キーを生成します(以下図)。
"4lIzzSbq#>J1v*CSIr#ofX3Bh%)f$3CQSdkz!vnUspXDIu4RJYNXVaE#%uS)"
生成した復号キーを用いて、メモリ領域[α]上のデータを1バイトごとに復号したデータで上書きしていきます(下図)。
この際、1バイトごとのループにつき無意味なprintf関数を3回呼び出します。復号のループ処理は最大 205,619回(0x32333回)行われるため、結果的に最大で約61万回(205,619回×3)のprintf関数の呼び出しが発生することになります。
Contiランサムウェアがこうした処理を入れている背景ですが、もし解析ツールや解析システムなどを用いてプロセスから呼び出される全てのAPIの呼び出しを動的に監視した場合、監視している分だけ実際よりも一つのAPIの呼び出しに遅延が発生します。大量の無駄なAPI呼び出しをわざと生み出すことで結果的に膨大な処理時間を発生させることになるため、これは動的解析に対する一種の解析妨害といえます。
実際に呼び出されるAPIを動的解析により監視した結果、大量のprintf関数でログが埋め尽くされ膨大な時間を要することがわかります(下図)。
図 12 大量のprintf関数の呼び出しによる動的解析の妨害
シェルコードの解説
上記の処理が終わりメモリ領域[α]に復号されたデータはシェルコード(単独で動作するように機械語で作られた一連の不正コード)となっており、先頭アドレスがcall命令で呼び出されることで実行処理がEXEからシェルコードに遷移します(下図)。
実行が遷移したシェルコードは、WindowsAPIを使用するための準備に入ります。
自身のプロセスのPEBを辿っていき、InLoadOrderModuleListを参照することで、プロセス起動時にロードされた順の各DLLにおけるベースアドレスを取得していきます(下図)。
図 14 シェルコードがPEBを辿りモジュールのベースアドレスを取得
その後、各DLLが提供するAPI名を一つずつ取得していきます(下図)。
図 15 ベースアドレスを元にエクスポート関数のリストを取得
シェルコードの中には、使用したいWindowsAPI名がハッシュ値化した状態でハードコーディングされています(下図)。
シェルコードはDLLから取得した実際のAPI名の文字列をハッシュ計算し、ハードコーディングされているハッシュ値と比較していくことで、必要なAPIを特定していきます(下図)。
図 16 取得したAPI名をハードコーディングされているハッシュ値と比較
その結果、以下のように必要最低限のいくつかのWindowsAPIのアドレスを取得します(下図)。
図 17 ハッシュ値とそれに対応するWindows API名
ここで一度シェルコードの内部構造についてざっくりと解説しておきましょう。
Contiランサムウェアのシェルコードは以下の図のような3層構造になっています。
シェルコードにはDLLが含まれ、さらにそのDLLの内部にはEXEが埋め込まれています(下図)。
Contiランサムウェアは2回のReflective PE Injectionというテクニックを重ねて使用することでこれらのDLLやEXEをディスク上に書き込むことなくファイルレスで実行させます。
Reflective PE Injectionとは実行ファイルをハードディスクに書き込むことなく、つまりファイルレスでプロセスにロードさせ実行する技術です。この辺りの処理についてものちほど詳しく解説します。
上記の3層構造を実際に理解するため、シェルコード内に存在するDLL(以降ではReflective DLLと呼ぶ)の内部構造をさらに見てみましょう。
シェルコードから取り出したReflective DLLの構造を見ると、[.data]セクションがファイルサイズの大部分を占めていることがわかります(下図)。
さらに[.data]セクションに含まれるバイナリデータを確認するとPEファイルのMZヘッダの先頭を示す[4D 5A]の2バイトが確認できます。つまりこの結果から、DLLの中にさらにPEファイル(EXE)が含まれていることがわかります。
図 19 Contiランサムウェアのシェルコードに含まれるDLLの構造
では、話をシェルコードの動きに戻しましょう。
シェルコードは、Reflective DLLのヘッダ(Optional headers->SizeOfImage)を参照することでDLLの全体サイズを取得し、そのサイズ分のメモリ領域(以降ではメモリ領域[β]と呼ぶ)を確保して0で一度初期化します。この際にメモリ確保のために使用するVirtualAlloc関数では実行権を持たないRead/Writeのみのアクセス権でメモリを確保します。
ここからシェルコードによる1度目のReflective PE Injectionの処理に入っていきます。
シェルコードは確保したメモリ領域[β]にReflective DLLのヘッダの一部をコピーします。
以下の比較図の通り、メモリ領域[β]にコピーされたデータにはMZヘッダなど一部のデータが存在しないことがわかります(下図)。
図 21 シェルコードは確保したメモリ領域[β]にDLLのヘッダの一部をコピー
上記のヘッダコピー処理では、具体的には以下に挙げる各情報がシェルコード内のReflective DLLからメモリ領域[β]へコピーされます。
- PEヘッダ(NT headers)へのオフセット (DOS Header の"File address of new exe header"の値)
- PEヘッダ(NT headers)のうち、Data Directories以外
- セクションテーブル
続いて、メモリ領域[β]の後方にReflective DLLの各セクションのデータをコピーしていきます(下図)。なお、以下の図に示したとおり、Reflective DLLの[.data]セクションに位置づけられる領域に、上で少し触れたEXE(以降ではReflective EXEと呼ぶ)が含まれています。
図 22 シェルコードは確保したメモリ領域[β]にDLLの全てのセクションデータをコピー
シェルコードはその後、当初実行権をつけずに確保したメモリ領域[β]にコピーしたReflective DLLにおいて、そのコードセクションにあたる[.text]セクションのメモリ領域を指定し実行権を付与することで、Reflective DLLのコードを実行できるようにするための準備を行います(下図)。
図 23 シェルコードはコピーしたDLLデータの一部に実行のアクセス権を付与
その後、シェルコードはReflective DLLの.textセクションをcall命令で呼び出すことで、実行処理がシェルコードからReflective DLLに遷移します(下図)。
この流れでおわかりのように、このReflective DLLはハードディスク上に作成されません。つまり、ファイルレスでDLLが実行されることになります。これがReflective PE Injectionの1回目に該当します。
なお、Reflective PE Injectionを用いて実行されたReflective DLLは、LoadLibraryなどの通常の手順でロードされていないため、プロセス調査ツール等を用いて調べてもプロセスのロード済みモジュール一覧などに表示されることはなく、存在に気づくことが困難となります。
シェルコードの処理は以上となり、これからReflective DLLの挙動に入っていきます。
Reflective DLLの解説
シェルコードによって実行されたReflective DLLは、自身の[.data]セクションの領域(つまりReflective EXEが含まれる領域)を参照し、新たに確保したメモリ領域(以降ではメモリ領域[γ]と呼ぶ)にコピーしていきます(下図)。
なお、この際確保したメモリ領域[γ]にはこれまで同様まだ実行権を付与しません。
その後、Reflective DLLは、領域[γ]にコピーしたReflective EXEのコードセクションとなる[.text]セクションに実行権をつけることでコードとして実行できる状態にします。
図 26 コピーしたメモリ上のEXEの一部に実行のアクセス権を付与
これでReflective EXEを実行できる状態になりました。
Reflective DLLは、Reflective EXEの[.text]セクションをcall命令で呼び出すことで実行し、実行処理がReflective DLLからReflective EXEに遷移します。
ここの流れでもおわかりの通り、Reflective EXEはハードディスク上に作成されず、ファイルレスで実行されることになります。つまり、これが2回目のReflective PE Injectionとなります。
なお、実行されたReflective EXEは通常の手順でプロセスとして実行されたわけではないため、プロセス調査ツールなどで見てもプロセス一覧には当然表示されません。
図 27 メモリ上のDLLは実行権を付与したEXEの領域を実行
このように、Contiランサムウェアは2回のReflective PE Injectionを重ねて利用することで、検知や解析から逃れつつ、ランサムウェアとしてのメイン機能であるReflective EXEの実行を最後に呼び出すわけです。
以上でDLLの役目は終了し、心臓部であるReflective EXEの挙動に入っていきます。
Windows APIの暗号化による解析妨害
ここから、Contiランサムウェアのメイン機能となるReflective EXEの挙動を解説していきますが、その前にReflective EXEが行ういくつかの解析妨害機能をはじめにご紹介しましょう。
Reflective EXEは、動作中に使用する全てのWindows APIを暗号化しています。
具体的には以下の図のように固有のハッシュ値と1バイトキーを用いて、使用するたびに必要なWindows APIのアドレスを復号した上で呼び出します。全てのWindows APIが暗号化されているため、静的解析はもちろん動的解析においても呼び出されるWindows APIを容易に得ることは困難となります。
詳しくは後述しますが、さらにWindows APIに渡す引数の文字列も、全ての文字列をそれぞれ異なる暗号化関数で暗号化しており、文字列を使用するたびに復号します。
Contiランサムウェア自動解析スクリプト
そのため、今回Contiランサムウェアを解析するにあたり、Contiランサムウェアが呼び出すAPIの復号結果を列挙するオリジナルの自動解析スクリプトを新たに作成しました。
この自動解析スクリプトは、暗号化されたAPI名だけでなく、それぞれ個別に暗号化されている引数の文字列についても、復号後の文字列を一部併せて出力します。
さらに、それぞれのAPIに対応するハッシュ値と1バイトキーの組み合わせも末尾に出力します。
以下はその出力ログのサンプル画面です。
また本自動解析スクリプトには補足機能として、静的解析ツールであるIDA Proの逆コンパイラ画面(IDB)の各ハッシュ値の右横に、復号後のDLL名とAPI名をコメントとして自動入力する機能もつけています。
以下は自動解析スクリプトを実際に動作させている様子ですが、Contiランサムウェアが呼び出すAPIとその引数が復号された状態でログとして出力されます(画面中央の黒い部分が出力ログ)。
上記の自動解析スクリプトは以下のGitHubにアップしています。
URL:https://github.com/AgigoNoTana/resolve_conti_API/blob/main/resolve_conti_API.py
文字列の暗号化による解析妨害
先ほど少し触れましたが、Contiランサムウェアは挙動の中で使用する全ての文字列をそれぞれ固有の計算により暗号化しており、文字列ごとに用意された関数で使うたびに復号して使用します。
では、その文字列の暗号化について少し踏み込んで詳しく解説しましょう。
以下の図は、ある一つの文字列("explorer.exe"という文字列)を復号する処理をピックアップしたものですが、暗号化された文字列をその文字列専用に用意された個別の計算処理で1バイトごとに復号することを示しています。
下図におけるアセンブリ言語をよりわかりやすく書き直したものを下図右下に載せていますが、図に掲載した通りの計算に従うと、例えば0x7Cという値は計算され最終的には0x65となり、ASCIIの"e"というアルファベットになることがわかります。
上記の文字列に対する計算を一つの式にした場合、以下の図のようになります。全てのバイトを同様の計算式で復号していくと、"explorer.exe"という文字列が表れます(下図)。
なお、復号した文字列は使用した後再利用されず必要となるたびに復号されるため、メモリ上の文字列検索やメモリダンプなどを取得してもContiランサムウェアが使用する文字列をまとめて得ることは出来ません。
上記でも述べた通り、Contiランサムウェアは文字列の暗号化に共通の関数を使用しておらず、全て異なる計算で暗号化しています。
例えば、別のある文字列では以下のような計算で暗号化と復号を行っています(下図)。
下図で割り出した計算式をさきほど上で解説した計算式と比較すると計算式が異なることがわかります。つまり、全ての文字列に対して個別の暗号計算を用いることで共通の復号ロジックを利用して割り出すような解析手法を妨害しており、非常に手が込んでいるといえるでしょう。
なお、以下の図の計算処理では「__ProviderArchitecture」という文字列に復号されます。
以上で解説したように、これから言及していくContiランサムウェアのReflective EXEは呼び出す全てのWindows APIと全ての文字列を暗号化しており、都度復号して使用しています。
Reflective EXEの解説
前置きが長くなりましたが、ここからReflective DLLによって最後に実行されるReflective EXEの処理の解説に入っていきます。
ここまでの解説をまとめると、ContiランサムウェアのEXEファイルは以下のような構造となっており、最終的にメモリ上でのみ実行されるReflective EXEがContiランサムウェアのメイン機能となります。
図 35 Contiランサムウェアの全体構造と主要本体の位置
Reflective EXEのバイナリデータからは以下の通り、ランサムウェア開発者の開発環境を示す情報が確認でき、今回のContiランサムウェアがV3(バージョン3)として開発されたことがわかります。
Reflective EXEは実行されるとすぐにコマンドライン引数をチェックします。
以下はコマンドライン引数のチェック処理を抜粋したものですが、複数のコマンドライン引数により処理が細かく分岐します(以下図)。
上記の図にあるコマンドライン引数は以下の意味を持ちます。
- -p:特定のフォルダのみ暗号化する際にパスを指定
- -m:暗号化する範囲(ローカルやネットワーク等)の指定
- -log:失敗した処理のログ出力を指定
- -size:大きいファイルに対し分割して暗号化する際の割合に関する設定
- -nomutex:ミューテックスを作成しない
ここで注目すべきなのは本ランサムウェアにこうした手動操作による実行を示唆するコマンドライン引数が用意されている点です。つまり、単体で実行させるだけでなく、人の手による暗号化のための道具としての利用も考慮して開発されたランサムウェアであることがわかります。
これらのコマンドライン引数はあくまでオプションであるため、何も引数を渡さずに実行すると通常のランサムウェアとしての挙動を行います。
例えば、[-log]引数をつけて実行することで、以下の図のように実行ログが出力されます(下図)。
実行ログには主に暗号化に失敗したファイルが出力されるため、攻撃者がシステムに侵入しランサムウェアを展開した後、リアルタイムに作業成功状況を把握する目的などが垣間見えます。(もちろん[-log]オプションを付けない場合は何も出力されません。)
図 38 実行ログの出力に対応しているContiランサムウェア
以降では、何もコマンドライン引数が付与されずに実行された場合の挙動を解説していきます。
多重感染防止
Reflective EXEは特定のミューテックス(排他制御のための仕組み)を作成することでContiランサムウェアのプロセスが多重起動(多重感染)しないように防止します。
つまり、該当のミューテックスが既にシステムに作成されていた場合、Contiランサムウェアは何もせずに終了します。
なお、[-nomutex]の引数をつけて起動された場合はミューテックスを作成しません。
Reflective EXEによるファイル暗号化
ここからは、Contiランサムウェアのメイン機能であるReflective EXEによるファイル暗号化について解説していきます。
まずはファイル暗号化の全体処理の流れを把握いただくために一つの図にまとめました(下図)。
Contiランサムウェアは一つ一つのファイルに対し、以下の順序で処理を行うことでファイルを暗号化していきます。
下図に記載した個々の処理やキーワードについては、以降で詳細に解説していきます。
まず、(上図)「①除外対象チェック」ですが、Contiランサムウェアは以下の図に記載した文字列を含むフォルダやファイルを除外対象とし暗号化を行いません。いずれの文字列もStrStrlW関数を用いて比較されるため、これらの文字列がファイルパスの一部に含まれるだけで除外されます(下図)。
のちほど詳しく触れますが、Contiランサムウェアはファイルの拡張子やサイズによって暗号化の手法を変化させる動きがあるため、それらの拡張子についても先にまとめてご紹介しておきましょう。
まず以下の図にあげる拡張子はどのようなサイズであってもサイズチェックなしに全体を暗号化します。これはつまり、Contiランサムウェアの攻撃者が明確に暗号化すべきだと考えている対象ファイルのリストと言い換えることができるでしょう。データベースに関連した拡張子が多いことから比較的大きな組織やファイルサーバ等の重要なデータが一元管理されている場所への攻撃を念頭に置いている事が伺い知れます。
また以下に挙げる拡張子リストは、ファイルサイズをチェックせずとも無条件に拡張子だけでサイズが大きいと推定し、常にファイルサイズが大きい場合に用意された暗号化処理を行います。
後述しますがファイルサイズが大きい場合に用意された暗号化処理ではファイルの一部の暗号化を省略するため、場合によってはデータが全く暗号化されないケースがあります。
そのケースの具体例をあげると、上記のリストに含まれる[.vmdk]という拡張子を、実際はファイルサイズが小さなテキストファイルにつけたとします。すると場合によっては、暗号化された後のファイルの中身を見ても、以下の図のように元のファイルデータが暗号化されずに残る場合があります。これはContiランサムウェアの開発者が”上記の拡張子を持つファイルはサイズが大きいだろう”と大雑把にコーディングしてしまった設計ミスとも言えるでしょう。ただし、確かに上記の拡張子を持つファイルにおいてサイズが小さいケースは実際稀であり、攻撃者にとって致命的と言えるようなバグではありません。
構造体で管理するファイル暗号化
Contiランサムウェアは、暗号化対象ファイルを独自の構造体(以降でCONTI構造体と呼ぶ)で扱うことで暗号化処理を効率化しています。
CONTI構造体には、一つのファイルを暗号化するために必要な様々な情報が格納され、各暗号化処理で必要に応じて参照されます。
以下の図は静的解析の結果から再現したCONTI構造体の定義とその役割となります(下図)。
上のCONTI構造体を見ても分かる通り、Contiランサムウェアはファイルの暗号にChaCha暗号と呼ばれる暗号化方式を使用します。以下はChaCha暗号の処理に該当する処理部分ですが、ChaCha暗号の特徴であるローテート操作で使用される定数が確認できます。
ChaCha暗号はラウンドと呼ばれる処理の回数により複数の方式が存在しますが、ContiランサムウェアはChaCha8を使用します。
以下はContiランサムウェアのChaCha暗号の全体処理を示した図ですが、2つのラウンド処理(通称double-roundと呼ばれる)が4回のループで処理されるため、2×4=8ラウンド、つまり、ChaCha8であることがわかります(下図)。
Contiランサムウェアは暗号化対象ファイルを前述の通り、ChaCha暗号で暗号化し、その鍵であるChaCha暗号鍵をRSA公開鍵で暗号化して、暗号化したファイルの末尾に追加します(下図)。
そのため、ファイルを暗号化する前にRSA公開鍵を使用するための準備作業に入ります。
図 48 ファイルをChaChaで暗号化し、ChaCha鍵をRSA公開鍵で暗号化
RSA公開鍵を使用するためにContiランサムウェアは、CSP(暗号化サービスプロバイダ)と呼ばれるWindowsの暗号化エンジンを利用します。
その際、暗号化方式を指定する文字列(暗号化プロバイダー名)として「Microsoft Enhanced RSA and AES Cryptographic Provider」という文字列を使用しますが、例によってこの文字列は固有の暗号計算で暗号化されており復号して使用します(下図)。
復号した暗号化プロバイダー名をCryptAcquireContextA関数に渡すことで鍵の格納場所を示す「キーコンテナ」のハンドルを取得します。
RSA公開鍵のバイナリデータ(0x1000バイト)はContiランサムウェアのReflective EXEにハードコーディングされています。CryptImportKey関数の引数にRSA公開鍵のバイナリデータを渡すことでインポートし、RSA公開鍵の「キーハンドル」を取得します(下図)。
これでRSA公開鍵を使用する準備が整いました。
Contiランサムウェアは暗号化するファイルパスを格納したCONTI構造体と、先程取得した各情報(RSA公開鍵のキーコンテナハンドルとキーハンドル)を引数にして、独自のファイル暗号化関数を呼び出します(下図)。
この時点では、CONTI構造体のメンバ変数にはファイルパスのみが格納されている状況ですが、ファイルの暗号化は最終的にCONTI構造体のメンバ変数が全て埋まった状態で開始される必要があるため、これ以降で他のメンバ変数(ChaCha暗号鍵などの情報)を埋めていく作業に入ります。
Contiランサムウェアは一つ一つのファイルを個別のChaCha暗号鍵で暗号化するため、その作業に最も必要なChaCha暗号鍵に関わるデータを生成する処理に移っていきます。
まず、CryptGenRandom関数を用いて、64ビット、256ビットのランダムなデータを生成します。
生成したランダムデータは一度CONTI構造体のメンバ変数である[random64]と[random256]に一時的に格納された後、[chacha_nonce]と[chacha_key]というCONTI構造体のメンバ変数にそれぞれの値が格納されます。つまり、生成したこれらのランダムデータはそれぞれChaCha暗号の際に使用する"使い捨て乱数"と"ChaCha暗号鍵"に相当します。
さらに、ChaCha暗号で用いられる定数として知られる"expand 32-byte k"という文字列もCONTI構造体のメンバ変数である[chacha_cons]へ格納します(下図)。
その後、 [chacha_key](ChaCha暗号鍵)と[chacha_nonce](使い捨て乱数)をCONTI構造体のメンバである[encrypted_keybuf]に一時的にコピーした後、RSA公開鍵を使用してCryptEncrypt関数で[encrypted_keybuf]のデータを暗号化します。
つまり、個々のファイル暗号化で使用するChaCha暗号鍵をRSA公開鍵で暗号化していることを意味します(下図)。
そして最後に、暗号化対象ファイルをCreateFileWで開きファイルハンドルをCONTI構造体のメンバ変数[hFile]に格納、GetFileSizeExで取得したファイルサイズを残るメンバ変数である[FileSize]に格納します(下図)。
ここまでの処理により、一つのファイルに対応するCONTI構造体の全ての値が埋まった状態となります。
以上の処理で、一つのファイルを暗号化するための準備が整いました。
ここから実際の暗号化処理に入っていきます。
Contiランサムウェアははじめに、暗号化対象ファイルの末尾にCONTI構造体の[encrypted_keybuf]、つまりRSA公開鍵で暗号化したChaCha暗号鍵のデータを追記します。
そしてさらにその後ろに、復号の際に必要なファイル情報が記載されたContiランサムウェア特有のフッター(他のランサムウェアに見られるような固有の文字列は含みませんがConti特有であるため、以降ではこのフッターを"Contiマーカー"と呼びます)を追記します(下図)。
Contiマーカーは10(0xA)バイトの固定サイズであり、以下の図にまとめたように「暗号化された状態を示す情報」である2バイトと、「暗号化前のファイルサイズ情報」である8バイトで構成されています。
Contiランサムウェアは後述の通り、ファイルサイズや拡張子などで暗号化方式を選別しているため、ファイルを復号する際もどのようなロジックで暗号化されたファイルなのかを知る必要があります。そのために、CONTIマーカーの最初に2バイトにそれを示すデータを含ませているわけです(下図)。
上でContiランサムウェアがファイルの種類やサイズにより暗号化方法を選別すると言及しましたが、具体的には以下のような状況に分かれます(下図)。
つまりはファイルサイズや種類などで分類して暗号化の効率(処理スピード)を上げていると考えられます。
ここまでの処理ではまだフッターが追記されただけでファイルのボディ部分は暗号化されていません。以降でファイルのボディ部分の暗号化に入ります。
まず、あらかじめVirtualAlloc関数により確保していたメモリ領域に、ReadFile関数で暗号化対象ファイルを読み込みます。
Contiランサムウェアは読み込んだメモリ領域(つまりファイルのボディ部分)に対し、CONTI構造体に含まれる各種情報を参照しながらChaCha暗号で暗号化していきます(下図)。
そして暗号化したメモリデータを実ファイルへ上書きすることにより書き込みます。
図 57 CONTI構造体を参照して行うファイルボディの暗号化
最後に、暗号化が完了したファイルの拡張子をMoveFileW関数により「<元の拡張子>.KCWTT」に変更します(下図)。
以上の処理により、Contiランサムウェアに暗号化されたファイルの内部構造は、暗号化前後で比較すると以下のようになります(下図)。
これまで解説した内容と以下の図からもわかる通り、Contiランサムウェアに暗号化されたファイルは元のファイルサイズから534バイト(524+10)分だけファイルサイズが増加します。
図 59 Contiランサムウェアにより暗号化されたファイルの内部構造
また、一つのファイルを暗号化する際のファイル操作の詳細は以下のような流れとなります(下図)。
ここまでで、一つファイルに対する暗号化の処理を詳しく解説してきましたが、同様にCONTI構造体を用いながらこのような処理を全てのファイルに対して行っていきます。
また、ファイルを暗号化する際ですが、感染端末のCPUにおけるプロセッサ数を取得し、2倍した数のスレッドを作成し、マルチスレッドで処理を行うことで暗号化作業を高速化します(以下図)。
Windows Restart Managerの悪用
一般的にランサムウェアがファイルを暗号化する際、ファイルが使用中で開かれていると暗号化が行えないため、そのファイルを開いているアプリケーションを閉じる必要がありますが、Contiランサムウェアはアプリケーションを終了させるために非常に効果的な手口であるWindows Restart Manager(再起動マネージャー)と呼ばれるOSの機能を悪用します(下図)。
暗号化時にアプリケーションを閉じる挙動は従来のランサムウェアにもありましたが、従来の手口はあらかじめ強制終了対象のプロセスやサービス名を並べた強制終了リスト(例えばWordやExcel、データベースソフトなど)をハードコーディングしており、それを用いて強制終了関数を呼び出すことで強制終了させる手口が一般的でした。しかし従来のその方法では、強制終了リストに含まれていないアプリケーションを終了することができず、またわかりやすい強制終了リストをランサムウェア内にハードコーディングする必要や、強制終了に関わるわかりやすいWindowsAPIを呼び出す必要があったため、シグネチャ検知や挙動検知のリスクがありました。
Contiランサムウェアが代わりに採用したWindows Restart Managerは、本来その名の通り、起動中のアプリケーションをWindowsOSの再起動時やシャットダウン時に自動的に閉じるために実装されたOSの正規機能です。このWindows Restart Managerを利用することで、ファイルを開いているアプリケーションを全て特定できるようになり、強制終了リストを持たなくてもOSの正規機能を用いて効率的にもれなく終了させることができるようになりました(下図)。
また、わかりやすい強制終了系のWindows APIを使用せずに実現できるメリットがあり、アプリケーションに対しては不審な強制終了ではなく"OSによる再起動もしくはシャットダウン時の終了命令"と誤解させることができるようになります。つまり、一般アプリケーションのみならずセキュリティ製品によっては、不審なプロセスからのTerminateProcessなどの明確なAPIによる強制終了はブロックしてもWindows Restart Mangerによる正規のアプリケーション終了を防御できない可能性が出てきます(ユーザーがWindowsを再起動したりシャットダウンしたりする際に自動的に終了できないアプリケーションは逆に珍しいかもしれません)。
Windows Restart Mangerを悪用するランサムウェアはContiランサムウェアが初めてではありませんが、最近徐々に目立ってきている印象があり、やっかいな手口であると言えるでしょう。
図 62 Windows Restart Managerの悪用
ContiランサムウェアによるWindows Restart Mangerの悪用手順について、さらにより詳細に解説したものが以下の図です。
RmStartSession関数で開始したRestart Managerセッションに、RmRegisterResources関数を用いて暗号化したいファイルのパスを登録します。次に、RmGetList関数を呼び出すことで該当ファイルを使用中のアプリケーション情報をWindowsOSから得ることができます。その後、Contiはファイルを使用中のアプリケーションがexplorer.exeの場合のみ例外的に強制終了から除外するためにexplorer.exe以外のプロセスを選別した後、ファイルを使用中のアプリケーションをRmShutdown関数を介して間接的に強制終了させます(下図)。
図 63 Windows Restart Managerの悪用の詳細
以下の図はさきほど触れた、explorer.exe以外のプロセスを検索してリスト化する処理の様子です。
Windows Restart Managerによる強制終了からexplorer.exeを除外している背景は、もしexplorer.exeが強制終了させられてしまうと、被害ユーザーがエクスプローラーを介した端末操作ができなくなってしまい脅迫文を開いたり攻撃者へコンタクトを取らせたりする際に障壁となってしまうため、explorer.exeのみWindows Restart Managerによる強制終了の対象から除外しているものと推測します。
図 64 explorer.exe以外のプロセスをリスト化する処理
RmShutdown関数による強制終了の際、OS再起動やシャットダウン時において応答しないアプリケーションを強制的に終了させるために用意されたパラメーターである「RmForceShutdown」を渡すことで強制終了を実現させます(下図)。
Windows Restart Mangerの悪用における具体例を一つご紹介しましょう。
以下の図は「MicrosoftEdgeUpdate.log」というログファイルが暗号化されようとする際にContiランサムウェアがRmGetList関数を呼び出した直後のメモリの様子ですが、暗号化しようとした「MicrosoftEdgeUpdate.log」というログファイルが「MicrosoftEdgeUpdate.exe」というアプリケーションによって開かれていることをContiランサムウェアが把握した瞬間を示しています。この直後、「MicrosoftEdgeUpdate.exe」というアプリケーションは強制終了されます。
図 66 Windows Restart Managerが悪用された際の様子
なお補足となりますが、Windows Restart Managerを利用されることで偶発的にみられる分析者側の弊害として、状況によりマルウェア解析ツールなどがたまたま掴んでいるファイルが暗号化された際にマルウェア解析ツールが強制終了させられるという現象が稀に見られることがあります。ただしこれまでに解説したとおり、これは解析ツールを明確に妨害しようとして攻撃者が意図した挙動ではありません。例えば、以下の図は解析ツールであるIDA Proが「MSIGMSIZ.DAT」というシステムファイルをたまたま開いている際に、Contiランサムウェアが該当ファイルを含むフォルダの暗号化処理に入った際、RmGetList関数により該当ファイルを開いているIDA Proを認識しRmShutdown関数で強制終了させる際の様子です。当然、該当ファイルを開いていないタイミングであれば、IDA Proが強制終了されることはありません。これはその他のデバッグツールや調査ツールなども同様です。
システムの復旧妨害
説明が少し前後してしまいましたが、Contiランサムウェアは暗号化の前にシステムの復元(ボリュームシャドウコピー)を削除する挙動があります。これによりユーザーはシステムを過去の状態に復元できなくなります。ボリュームシャドウコピーの削除処理は、WMI(Windows Management Instrumentation)を用いて行いますが、前述の通りこの際もWMIを使用する際に必要な全ての文字列は異なる暗号化が施されており、それぞれ個別の復号関数で復号されます(下図)。
補足となりますが、上記のWMIによるボリュームシャドウコピーの削除操作は、イベントログの設定でWMIのイベントトレースを有効にしていた場合以下の図のようにイベントログに記録が残ります。
脅迫文の作成
Contiランサムウェアは、暗号化と並行して全てのフォルダ配下に「readme.txt」というファイル名で脅迫文を作成していきます(下図)。
図 70 全てのフォルダ配下に脅迫文の作成
以下はContiランサムウェアが作成する脅迫文の本文ですが、脅迫文の中で二重の脅迫を示す窃取データの公開に関して言及していることがわかります。
ネットワーク挙動
Contiランサムウェアは同一ネットワーク上の共有フォルダやネットワーク上の他の端末のファイルを暗号化する能力を持っている点が特徴的です。なおこのネットワーク挙動は、ランサムウェアそのものを拡散させるワーム機能ではなく、あくまでネットワーク越しの暗号化です(ただし結果的には端末内のファイルが暗号化されてしまうという点で同等の影響があると言えます)。
Contiランサムウェアは管理共有にSMBでアクセスすることで他の端末の中のファイルを暗号化していきますが、パスワードアタックなどの独自の認証突破機能は持っていないため、ネットワーク越しの暗号化は以下の図でまとめたような条件によって結果が異なります。あくまでContiランサムウェア本体が感染したサーバまたは端末から認証入力なしでアクセス可能な対象(端末や共有フォルダ)があればそれらが暗号化されていきます。ただし、例えば共有フォルダなどに認証が設定されていたとしても一度ユーザーが認証入力したなどで感染端末内に認証情報が記憶されている状況下であれば認証入力が不要となるため暗号化されてしまいます(下図)。
また、近年の攻撃ではドメインコントローラー(Active Directory)を狙って侵略するケースが少なくありませんが、デフォルト状態では管理共有が有効になっているため、ファイアウォールが適切に設定されていない環境では、ドメイン配下の全端末の管理共有に認証無しでアクセス可能となるケースがあり、その場合ドメインコントローラーを感染させることで、このネットワーク越しの暗号化機能によって結果的に配下全ての端末が暗号化されてしまう可能性があります。
他端末を暗号化している際のランサムウェアのプロセス内部ではローカルファイルと同じようにアクセスし暗号化していきます。以下の図はContiランサムウェアがネットワーク上の別の端末のファイルを暗号化している際のプロセスのハンドル一覧の様子ですが、IPアドレスを含むファイルパスに対しファイルアクセスしている様子がローカルと同じように確認できます。
上で触れたネットワーク上の他端末を探索する挙動についても深堀りして詳細を解説しましょう。
Contiランサムウェアは以下の図にあげた順で暗号化対象端末を選定し暗号化していきます(下図)。
まずGetIpTable関数によりARPテーブルの参照し、アドレスがプライベートIPアドレスであるものを探します。なお、この際に比較する数値の文字列もこれまでに解説したとおり、一つずつ異なる計算で暗号化されています。条件に合致するIPアドレスが見つかった場合、該当IPの4オクテット目を0〜254までインクリメントしながらARPパケットを送信します。
ARP解決できるIPアドレスが存在した場合、445番ポートへSMBプロトコルで疎通確認を行い、アクセスできる端末やフォルダが見つかった場合、FindFirst/FindNextFileなどの一般的なファイル操作関数を用いてローカルファイルと同じように探索し暗号化していきます(下図)。
次の図は、Contiランサムウェアが同一ネットワーク上の他の端末を探索している様子ですが、ARPパケットが1IPアドレスごとにインクリメントされながら送信されていることがわかります(下図)。
また以下の図は、見つかった端末にSMBリクエストを送信しアクセスしている様子ですが、C$などの管理共有へアクセスしていることがわかります。
その後、以下の図のようにネットワーク上の他の端末へのファイルアクセスもパケットキャプチャから確認することができます。
なお、管理共有に関する詳細とその対策については、過去の記事(※)をご覧ください。
(※標的型攻撃ランサムウェア「Ryuk」の内部構造を紐解く:/research/20191211/ryuk/)
ネットワークによる暗号化(動画)
より直感的に感じていただくために、Contiランサムウェアに感染した端末がネットワーク上の他の端末を暗号化する際の様子を収めた動画を用意して公開していますので併せてご覧ください。
Contiランサムウェアの挙動は以上であり、全ての暗号化が完了すると終了します。
一般に誤解されている可能性がある点として、データを盗み取るのはランサムウェアではなく、ランサムウェアを拡散させる前に攻撃者が手動で行います。そのため、以上で解説してきた通り、ランサムウェアの役割はファイルの暗号化や脅迫文の提示、システム復旧妨害などが主となります。
まとめ
ContiランサムウェアはRyukの後継とされていますが、過去に弊社ブログで公開したRyukの解析結果と比較すると、ネットワークを跨いだ暗号化に関する挙動が類似しているようにも見える一方、それ以外の挙動は大きく異なるように見え、もし同じ攻撃者が開発しているのであれば明らかに手口が複雑かつ高度になっていると言えます。
加えて、上記で解説した通り、Contiランサムウェアはファイルごとに異なる暗号化鍵で暗号化していますが、最近よく見られるランサムウェアのファイル暗号化手法には以下の2種類があります。
-
- 全てのファイルを環境ごとに共通した鍵で暗号化しその鍵(共通鍵)を公開鍵で暗号化するタイプ
- Contiのようにファイルごとに異なる鍵で暗号化した上でそれぞれの鍵を公開鍵で個別に暗号化するタイプ
前者の場合、公開鍵で暗号化した共通鍵を攻撃者に送付させれば、攻撃者は秘密鍵をばらすことなく、復号した共通鍵(を含む復号ツール等)のみを被害者へ送付することで復号が実現でき、さらに全ての被害組織で共通の公開鍵を使用するため検体が使いまわせるメリットがありますが、その一方で、メモリから共通鍵を抽出された場合に全てのファイルを一括で復号されてしまうリスクも存在します。
後者の場合、ファイルごとに異なる鍵で暗号化しているため、前述のリスクは軽減されますが、ファイルを復号する際の攻撃者の選択肢は「暗号化鍵を含んだ全ての暗号化済みファイル(または全ての暗号化鍵)を送らせる」か「秘密鍵(を含む復号ツール等)を渡す」かのほぼ2択になります。全てのファイルを送らせることは現実的ではないため、実質的に「秘密鍵を渡す」という選択しかありませんが、もし秘密鍵を渡してしまった場合その秘密鍵が共有されることで他の被害組織にも使い回されてしまう可能性があります。
つまり、ファイルごとに異なる鍵を使用する手法を取る場合、被害企業ごとに秘密鍵と公開鍵のペアを生成する必要があり、それは言い換えれば攻撃者が標的ごとに鍵のペアを用意・管理していることを意味し、明確にターゲットを絞って攻撃を行う前提でランサムウェアを開発したということを示唆しています。
なお、弊社が持つ脅威インテリジェンスを用いてContiランサムウェアの被害を受けた企業を複数調査した結果、RDPやVPNの不備、サーバソフトウェアの脆弱性などが過去に存在していた事実が複数の被害企業において確認されました。実際にそうした経路から侵入されたケースや、TrickBot/QakBotなどのマルウェアによってRDP等の認証情報を取得された可能性がある事例なども一般には報告されており、これまでに多方面で言われてきたランサムウェア対策と考慮すべき観点に変わりはありません。
Contiランサムウェアに手動操作が行えることを示唆する実行引数が用意されていることからも分かる通り、ランサムウェアは攻撃グループにとってはただの一道具という側面があるため攻撃の全貌を知る上ではごく一部分の内容に過ぎませんが、また一方でメインとなるその"道具"の詳細挙動を理解することは攻撃を知る上で必要不可欠であるとも言えるでしょう。
今回本記事で詳細に解説してきた内容は、Contiランサムウェアのみならず他のランサムウェアを理解する上でも共通してご参考いただける情報が含まれていますので、こうした情報が少しでも何かのお役立てになれば幸いです。
- ハッシュ値:707b752f6bd89d4f97d08602d0546a56d27acfe00e6d5df2a2cb67c5e2eeee30
- Contiランサムウェアv3自動解析スクリプト:
https://github.com/AgigoNoTana/resolve_conti_API/blob/main/resolve_conti_API.py - 感染動画URL:
https://youtu.be/PANnAIwct68
おすすめ記事