本サイトは、快適にご利用いただくためにクッキー(Cookie)を使用しております。
Cookieの使用に同意いただける場合は「同意する」ボタンを押してください。
なお本サイトのCookie使用については、「個人情報保護方針」をご覧ください。
すでに多くのニュースや公開記事で言及されているように、「LockBit 2.0」はリークサイトを持つ暴露型ランサムウェア攻撃グループの中で現在(2021年後半)最も活発である攻撃グループです。LockBit 2.0のリークサイト上では、いきなり窃取データが暴露されるのではなく、「被害組織名」とともに「暴露までの残り時間」をリアルタイムでカウントし被害組織に圧力をかけます。そのため、リークサイトに初めて掲載された時点においては被害組織と攻撃者間で金銭の支払いに関する交渉が行われているかもしくは交渉前の段階にあるケースが多いものと考えられます。
LockBit 2.0の開発者は自身のサイト上で、LockBit 2.0のランサムウェアが世界で最も暗号化速度が速く他のランサムウェアよりも優れていると、攻撃の実働部隊であるアフィリエイトに向け詳細にアピールしており、加えて他のランサムウェアには無い新しい技術も搭載しているという趣旨のコメントを掲載しています。(それを補足する関連情報として、海外で行われたLockBit 2.0の代表者へのインタビューでは「攻撃が速く実行されるほど、攻撃が撃墜されるリスクは少なくなる」と答えています。)
今回我々はこうした点を踏まえ、当該攻撃グループが使用するランサムウェア「LockBit 2.0」本体に着目し詳細解析を実施、本記事でその全ての解析結果を共有します。
なお、LockBit 2.0ランサムウェアに関する解析記事については海外ベンダーなどからすでに幾らか公開されていますが、それらの記事において言及されていない事実や、解析が及んでいない挙動などが多くあることを確認しました。
本記事では、そうした他の解析記事では触れられていない詳細情報や、全世界初となる情報なども多数盛り込み、 “世界で最も詳細なLockBit 2.0ランサムウェアの解析記事”(※)となるよう意識しました。
(※自社調べ。本記事執筆時点(2021年10月)かつ公開されている範囲において)
また、海外のみでなく日本国内においても多くの被害が出ていると想定される状況下において、LockBit 2.0ランサムウェアに関する日本語オリジナルによる詳細解析記事自体が依然ほとんど存在しない状況であるため、今回公開した本記事が幾らか国内を中心とした皆様の、お役に立てば幸いです。
※以降の文中において「LockBit 2.0」と記載した場合、攻撃グループ名ではなくランサムウェアを指します。
■概要
はじめにLockBit 2.0の特徴をまとめると、以下のような項目があげられます。
- I/O完了ポートの採用による暗号化処理の高速化
- ファイルの一部のみを暗号化することによる暗号化処理の高速化
- AES-NIが利用可能なCPU認識&活用による暗号速度の高速化
- 楕円曲線暗号(ECC)方式を採用したことによる暗号速度の高速化
- プリンタを利用した物理的な脅迫文の印刷(プリンタ一台当たり約1万枚)
- デスクトップ壁紙を利用したインサイダー(内部者)募集
- グループポリシーによるドメイン配下の全端末への自動展開
- 全ローカルドライブのネットワーク共有化
- 効果的な解析妨害(難読化、デバッガ検知、スレッドのステルス化など)
- あまり詳細が知られていないUAC Bypassによる権限昇格
- 入念なフォレンジック対策(単純な削除ではなく自身をハードディスクから抹消)
- 入念な感染継続性(暗号化中に端末がシャットダウンされることに備えた再開処理)
- 隠しパーティションのスキャンと強制ドライブマッピング
など
LockBit 2.0が自身のサイト上で自負しているランサムウェアの高速性については、詳細解析の結果、実際にファイルの暗号化を高速化させる複数の高度なテクニックが実装されていることを確認しました。
また、様々な手口のアイデアが機能として盛り込まれており、特に、ドメインコントローラ感染時に特別に発生させる「グループポリシーによる自身の拡散」という仕組みは、暴露系ランサムウェアとしては史上初の新機能であり、従来のアフィリエイト(攻撃の実働部隊)が手動で行っていた作業を置き換えることで、攻撃能力に個人差があるアフィリエイトの手動作業をできるだけ自動化し攻撃の質を平準化させる試みが感じられます。
また今回の解析で、LockBit 2.0が世界的に知られていないグラフィカルなインターフェイス(GUI)である「隠しウインドウ」を実装していることや、LockBit 2.0が複数の致命的なバグを抱えており、検体や被害環境によっては多重暗号化が発生することで、たとえ復号ツールが入手できたとしても復号が困難となるシチュエーションが存在することなど、多くの新事実も発見しました。
これらを含む、LockBit 2.0詳細解析の全貌は以下の解説をご覧ください。
■表層解析
LockBit 2.0はVisual Studio 2017で作成され、構造上はGUIアプリケーションとしてビルドされています。ただし実際には実行されても画面上には何も表示されずサイレントに感染が行われます。
また、単純な表層解析の範囲では一般的なGUIコンポーネントなどのリソースデータはEXEファイルから確認することはできません。
図 1 LockBit 2.0の開発情報
セクションとしては以下の2つの構造を持ち、一般的なGUIアプリケーションにみられるリソースセクションなども存在しません。
図 2 LockBit 2.0のセクション情報
アドレス空間配置のランダム化であるASLRは無効化された状態でビルドされています。
図 3 LockBit 2.0のEXE設定情報
その他、デジタル署名やプロパティ情報などは基本的に付与されていません。
■耐解析機能(解析を妨害する機能)
LockBit 2.0は解析を困難とするためにいくつかの耐解析機能(解析妨害テクニック)を実装しています。
LockBit 2.0は起動すると、すぐに自身がデバッグされていないかどうかをチェックし、デバッグ中であると判断すると無限ループし動的解析を妨害します。
具体的には、プロセス内に存在する様々な情報が格納されたPEB(Process Environment Block)という構造体の中の「NtGlobalFlag」という項目をチェックします。プロセスがデバッグ中の場合はこのNtGlobalFlagが0x70という値になるため、LockBit 2.0はNtGlobalFlagの値を取得し、「0x70」と比較することでデバッグ中であるかを判断します(下図参照)。
図 4 NtGlobalFlagによる解析妨害
また、LockBit 2.0は動作中、複数のスレッド(マルチスレッド)により処理を行っていきますが、その際にデバッガからスレッドを解析できないよう、スレッドをステルス化することで解析を妨害します。
このテクニックは新しいものではありませんが、非常に効果的な解析妨害方法であり、このアンチデバッグテクニックはBlackMatter等の高度なランサムウェアでも確認されています。
具体的には、NtSetInformationThread関数を使用し、指定したスレッドに ThreadHideFromDebugger (0x11という値)という特殊なフラグを設定する文書化されていない呼び出し方法により実現します。この値が設定されると、デバッガは該当フラグが設定されたスレッドからの情報を取得できなくなるため該当スレッド上のコードの解析ができなくなり、実質的にデバッガからスレッドを隠すのと同じ効果を得られます(該当スレッドを解析しようとしてもデバッガが素通りしてしまう上、無理やり解析を進めようとするとクラッシュします)(下図参照)。
図 5 ThreadHideFromDebuggerによる解析妨害
またLockBit 2.0は、Windows APIを名前ではなくハッシュ値で求める手法を採用し、Windows APIを隠蔽することで静的解析を妨害するテクニックも実装しています(下図参照)。
図 6 WindowsAPIのハッシュ比較による解析妨害
例えば、「Kernel32.dll」のアドレスは、以下のようにPEBのInLoadOrderModuleListから辿りハッシュ値をチェックすることにより導き出します(下図参照)。
図 7 Kernel32.dllのアドレスの割り出し処理
例えば、「gdiplus.dll」というDLLを「LoadLibraryA」でロードする際の一連の処理は、以下のような流れになります(下図参照)。LoadLibraryAのアドレスがハッシュ値で導き出された後、難読化されていた「gdiplus.dll」という文字列が復号され、引数に渡されます。
つまり、APIはアドレスをハッシュ値で参照し、APIで使用する文字列などは毎回復号して使用します。
その他、動作中に使用する全ての文字列についても同様に難読化されており、都度復号して使用します。
図 8 gdiplus.dllをLoadLibraryAでロードする際の処理例
文字列の復号は一つの関数で行われるわけではなく、文字列ごとにそれぞれ異なる復号方法を使用しています。以下の図は例として「Advapi32.dll」というDLL名の復号と、「user32.dll」というDLL名の復号処理の一部を抜粋したものです(下図参照)。
図 9 それぞれの文字列の復号は複数の異なる復号方法を使用する
以上のような複数の対策により、動的解析・静的解析の両面の観点からの解析を妨害します。決して数多くの耐解析機能が実装されているわけではありませんが、少ない中でより効果的な耐解析テクニックを組み合わせて利用しているといえます。
■感染環境の言語チェック
LockBit 2.0は感染端末の言語設定を取得し、CIS(独立国家共同体)を含むロシアを中心とした旧ソ連諸国の言語が設定されていないかどうかをチェックし、設定されている場合は感染せずに終了します(下図参照)。
この点については、開発者も自身のサイト上で”LockBit 2.0はポスト・ソビエト国家には感染しない”と明言しています。
言語チェックは、GetSystemDefaultUILanguage関数とGetUserDefaultLangID関数が用いられます。
図 10 特定の言語環境かどうかをチェックする処理とチェックする言語の一覧
なお一般的にあまり知られていませんが、こうした言語チェックを行わないLockBit 2.0の個体も一部存在することを今回の調査の中で確認しています。つまり、検体によっては言語チェックの機能自体が実装されていないケースがあり、ロシア語などの言語が設定された端末であっても以下の図のように感染してしまいます(下図参照)。
これは攻撃の実働部隊であるアフィリエイトがランサムウェアを攻撃対象組織に合わせチューニングしたうえで簡単にEXEを生成できるような仕組みを提供するRaaS(Ransomware as a Service)において、ランサムウェアに持たせる各機能の有効化/無効化をビルド時の設定で選択できる仕組みが背景にあるものと考えられ、そうした中で言語チェックを有効にしない状態でビルドされた検体が一部存在するものと推測しています。
図 11 ロシア語端末でLockBit 2.0が感染した様子
■UAC Bypassによる権限昇格
LockBit 2.0は、自身が管理者権限を持っていない状態で起動した場合、UAC(ユーザーアカウント制御)の昇格プロンプトを回避し管理者権限に昇格するテクニックである「UAC Bypass」を行います。
コード上では、以下のように管理者権限で実行された場合と、ユーザ権限で実行された場合で異なる遷移を取る分岐点があります(下図参照)。
図 12 UAC Bypassに関わる処理の分岐箇所と終了処理の構造
なお、この分岐点を解析作業時に容易に見つけ出すためには、実行時に通過したコード領域の差分分析を行うカバレッジログ分析が有効です。
以下のように「管理者権限で動作したコード領域」と「ユーザ権限で動作したコード領域」の差分を取り、「ユーザ権限でのみ動作したコード領域」を抽出させることで、UAC Bypassのコード箇所が抽出され特定できます(下図参照)。
図 13 カバレッジログを用いたUAC Bypass処理箇所の抽出と特定
LockBit 2.0は、上記のカバレッジログ分析において明らかとなった「ユーザ権限でのみ動作するコード領域」において、あまり広く詳細が知られておらず文書化されていない特定のCOM(Component Object Model)インターフェイスを悪用するUAC Bypassテクニックを用います。
以降では、そのUAC Bypassの流れを詳細解説します。
まずLockBit 2.0は自身が管理者権限で実行されているかどうかを確認します。
具体的には以下の方法で行います。
ZwOpenProcessToken関数にTOKEN_QUERY(0x8)アクセス権を指定した上でNtQueryInformationToken関数を呼び出しプロセストークンの構造体(TokenInformation)を取得、その際TokenInformationClassの「TokenElevation(0x14=20)」を引数に指定することでTOKEN_ELEVATION構造体を取得します。
その後、取得したTOKEN_ELEVATION構造体のメンバ変数である「TokenIsElevated」の値が0かどうかをチェックすることで管理者権限かどうかを判断します(下図参照)。
(自身が管理者権限で実行されていると判断した場合、[Process created with admin rights]という難読化された文字列をメモリ上に復号したのち、メインの処理を継続します)
図 14管理者権限かどうかを確認するためにプロセストークを取得
自身が管理者権限で起動していないと判断した場合、自身がUAC Bypassを行うことができる環境にあるかを確認する作業を行います。
まず先ほどと同様、ZwOpenProcessToken関数にTOKEN_QUERY(0x8)アクセス権を指定した上でNtQueryInformationToken関数を呼び出し、プロセストークンの構造体(TokenInformation)を取得します。
続いてCreateWellKnownSid関数に「WinBuiltinAdministratorsSid」(0x1A=26)という引数を指定することで「BUILTIN\Administrators」グループに対応するSID(セキュリティ識別子)を取得(作成)したのち、CheckTokenMembership関数を利用して、呼び出し元のプロセストークン内で「BUILTIN\Administrators」グループのSIDが有効になっているかどうかを確認することで、Administratorsグループに属しているかどうかをチェックします(属していない場合は本処理を終了します)。
つまりこの時点で、現在のユーザが”管理者権限を持たずAdministratorsグループに属しているユーザである”と判断できることになります。
なお、WindowsではAdministaratorsグループに属しているユーザがログオンする際、対となる2つのトークン(管理者としてのフルアクセストークンと制限されたユーザのトークン)が発行されます。
この2つのトークンのうち、現在の対となるトークンをTokenLinkedToken関数で取得することができます。
LockBit 2.0は自身のプロセスが管理者権限を持たず、かつ実行したユーザがAdministratorsグループに属していると判断すると、NtQueryInformationToken関数の引数に「TokenLinkedToken」(0x13=19)を指定することで、対となるトークン、つまり、フルアクセストークンを取得できるか確認します。
(この処理が失敗した場合、つまり対となるフルアクセストークンが存在しない場合は「Process created with limited rights」という文字列を復号しメモリ上に展開し、UAC Bypassの処理を終了します)
図 15 Administratorsグループに属しているかどうかをチェック
次に、LockBit 2.0はこれから行うUAC Bypassが利用できるOSバージョンであるかを確かめるため、GetVersion関数でWindowsのメジャーバージョンを取得し、「5」(Windows XP/2000/Server2003/Server2000)という値と比較、Windowsのメジャーバージョンが「5」ではない場合のみ処理を継続します(下図参照)。
(もし「5」に該当した場合は「Process created with limited rights」という文字列を復号しメモリ上に展開し、UAC Bypassの処理を終了します)
図 16 Windowsのメジャーバージョンを「5」と比較
以上の処理により、LockBit 2.0は自身がUAC Bypassを行うことができる環境にあると判断し、UAC Bypassの主たる処理に遷移します。
今回UAC Bypassにも利用される「COM」(Component Object Model)は、Process Status API(PSAPI)を使用して、プロセスのPEBという構造体のprocessParametersに含まれる実行パスに関する情報を読み取ることでCOMの呼び出し元プロセスを識別します。
そのため前準備として、LockBit 2.0は自身のプロセスが持っている実行パス情報を、以降で解説する方法により信頼できるプロセスの一つである「Explorer」の実行パスに強制的に書き換えることで偽装します。
(こうしたプロセスの偽装処理を「マスカレード」と呼びます)
そのため、まず以下のようにExplorer.exeのフルパス文字列を生成します(下図参照)。
図 17 マスカレード処理で使用するExplorerのパスを生成
続いて、LockBit 2.0はRtlInitUnicodeString関数を使用し、”C:¥Windows¥Explorer.exe”という文字列を自身のPEBのprocessParameters項目にあるImagePathNameに、また、“Explorer.exe”という文字列を自身のPEBのprocessParameters項目にあるCommandLineの項目に上書きします(下図参照)。
PEBのprocessParametersの各項目はUNICODE_STRINGという構造体の形式を取りますが、この際、LockBit 2.0はPEBの書き換えにRtlInitUnicodeString関数をそのまま使用することでUNICODE_STRINGに変換しつつ直接書き換えるという二つの作業を同時に行います。
図 18 LockBit 2.0が行う「マスカレード」(プロセス情報偽装)処理
つまり、これにより以下のようにLockBit 2.0のPEBにあるプロセスパラメータのパス情報がExplorerのパスに偽装(マスカレード)されます(下図参照)。
またこのマスカレード処理を、自身がロードしている全てのモジュール(DLL)に対しても同様に行います。
図 19 マスカレード処理によって改変されExplorerに偽装したLockBit 2.0のプロセス情報
続いてLockBit 2.0は、CoInitializeEx関数でCOMインターフェイスの使用準備をしたのち、CoGetObject関数で以下2つの特定のCLSIDを指定し、対応するオブジェクト名を作成します(下図参照)。
- 指定されるCLSID: {3E5FC7F9-9A51-4367-9063-A120244FBEC7}
- 作成されるオブジェクト名(COM昇格モニカ):”Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}”
- 指定されるCLSID: {D2E7041B-2927-42fb-8E9F-7CE93B6DC937}
- 作成されるオブジェクト名(COM昇格モニカ):”Elevation:Administrator!new:{D2E7041B-2927-42fb-8E9F-7CE93B6DC937}”
これらのCLSIDはUAC Bypassの観点で脆弱であることが一部で知られている、それぞれCMSTPLUAとIColorDataProxyという自動昇格が有効なCOMインターフェイスに紐づいています。
図 20 COM昇格モニカの作成
上記のオブジェクト名(COM昇格モニカ)がCoGetObject関数の引数に渡され呼び出されることで、COMのホストプロセス(COMサロゲートプロセス)である「Dllhost.exe」(正規プロセス)が以下の引数で起動します(下図参照)。
図 21 COMサロゲートプロセスが起動する様子
その後、LockBit 2.0は(Dllhost.exeを経由し)以下のレジストリ(今回利用するCOMに関連したDisplayCalibratorというレジストリ)を書き換えることで、LockBit 2.0のEXEがUAC Bypassを行える状態にします(下図参照)。
図 22 LockBit 2.0がUACbypassのために書き換えるレジストリ場所
以下は、書き換える処理と、書き換え前後のDisplayCalibratorのレジストリ値を比較した様子です(下図参照)。
図 23 LockBit 2.0により書き換えられる前後のレジストリ値「DisplayCalibrator」
その後上記設定が反映される関連されたCOM処理を呼び出すことで管理者権限に昇格したdllhost.exeがLockBit 2.0を起動させます(下図参照)。
図 24 DllHost.exeがLockBit 2.0を起動する様子
その際管理者権限が継承され、結果的にLockBit 2.0が自動的に権限昇格した状態で新たなプロセスとして起動することになります(下図参照)。
図 25 UAC Bypassにより権限昇格が行われた瞬間の様子
以上が、LockBit 2.0が用いる、あまり広く詳細が知られておらず文書化されていない特定のCOMインターフェイスを悪用したUAC Bypassのテクニックの仕組みです。
■多重起動を防ぐミューテックス(Mutex)の作成
LockBit 2.0は、アプリケーションが多重起動を防止する際によく使用するミューテックス(Mutex)を作成することで、自身のプロセスが多重に起動することを防ぎます(下図参照)。
図 26 LockBit 2.0が作成するミューテックス
■不意のシャットダウンに備えた暗号化の再開手口
LockBit 2.0は、ユーザがインシデントに気付き端末を急いで強制的にシャットダウンするケースに備え、端末が再起動された際も暗号化が再開できるよう、レジストリにあるHKCUのRunキーに自身のパスを登録することで、自動起動設定を行います。この自動起動のためのレジストリ設定は、暗号化がすべて完了すると削除されます。つまり、暗号化の途中でシャットダウンされても端末の再起動後に暗号化が再開できるようにしており、念を入れて執拗にすべてを暗号化しようとする攻撃者の意図が垣間見えます(下図参照)。
図 27 再起動時に再開できるようにRunキーを一時的に設定
■シャットダウン時に最後まで居残る手口
またLockBit 2.0は、ユーザが端末をシャットダウンしようとした際に最後まで居残ることができるようにします。
具体的には、SetProcessShutdownParameters関数を使用し、自身のプロセスがシャットダウン時に終了するプロセスの中で最後となるよう、シャットダウン時の終了順序を設定します。
これにより、端末のシャットダウン時に最後までシステムに居残ることで、できるだけ多くのファイルを暗号化しようとします。
図 28 シャットダウンパラメータの変更
■プロセスの強制終了とサービスの停止
LockBit 2.0は動作中、繰り返し実行されるスレッドの中でプロセスの強制終了を定期的に行います。
強制終了の対象プロセス名となる137個の文字列をメモリ上に復号/展開して用意します(下図参照)。
図 29 強制終了対象プロセスの展開
プロセスの強制終了は、ネイティブAPIであるZwTerminateProcessが利用されます。
図 30 プロセスの強制終了処理
以下は、LockBit 2.0が強制終了対象とする137個のプロセス名一覧となります(下図参照)。
これらには、インシデント調査に使用されるプロセスや、業務アプリケーション、データベース系アプリケーションなどのプロセスが含まれており、これらのプロセスを強制終了させることでLockBit 2.0の暗号化作業がスムーズに行われるようにします。
図 31 LockBit 2.0が強制終了する対象プロセス名リスト
また、サービスの停止も行いますが、その際ControlService関数にSERVICE_CONTROL_STOPを引数として渡すことで実施します(下図参照)。
図 32 サービスの強制停止処理
LockBit 2.0が停止させるサービス名は以下の79個であり、データベース系アプリケーション、バックアップ系アプリケーション、セキュリティ製品などのサービスが含まれています(下図参照)。
図 33 停止対象となる79個のサービス名一覧
■ゴミ箱を空にする
LockBit 2.0にはごみ箱を空にする挙動があり、全てのドライブにあるごみ箱をSHEmptyRecycleBin関数で空にしていきます(下図参照)。
図 34 全てのごみ箱を空にする処理
■隠しパーティションのスキャンと強制ドライブマッピング
LockBit 2.0は、FindFirstVolumeWおよびFindNextVolumeWで、ローカルドライブにマッピングされていない隠しパーティションを含むコンピュータ上の全てのボリュームをスキャンし、マウントされていない場合はSetVolumeMountPointWで空きのあるドライブ名を指定しドライブマッピングしていきます。
これにより以下のように通常は論理ドライブとして見えない隠しパーティション領域も強制的に論理ドライブとしてマウントされてしまいます。この結果、隠しパーティションであってもLockBit 2.0の暗号化対象として認識されるようになります(下図参照)。
図 35 隠しパーティションの強制ドライブマッピング
■ファイル暗号化処理の高速化
冒頭で触れた通り、LockBit 2.0の開発者は自身のサイト上で、ほかのランサムウェアと比較しLockBit 2.0が世界で最も高速であると自負しアピールしています(下図参照)。
図 36 最も高速であると自負するLockBit 2.0のサイト上のアピール文
そのため、LockBit 2.0のファイルの暗号化に関わる処理を詳しく調べたところ、実際にファイルの暗号化処理を高速化するための複数のテクニックが実装されていることを確認しました。
■「I/O完了ポート」の採用による処理の高速化
まず一つ目に挙げられるのは、「I/O完了ポート」(I/O Completion Port, IOCP)の採用によるマルチスレッドの高速化です。
「I/O完了ポート」とは、マルチスレッドの処理において、ファイルの読み書きなどCPUの範囲外での待機が発生するような、CPUから見ると時間を要する入出力処理(I/O)でCPUを無駄に待機させず、空き時間でほかの処理に効率的にリソースを割り振り管理するための仕組みで、サーバアプリケーションなどで採用されている高速化の仕組みです(下図参照)。
図 37 LockBit 2.0で使用されている「I/O完了ポート」の概念と仕組み
従来の一般的なマルチスレッドによる並列方法ではスレッドが消費するリソースやスレッドの開始・終了に伴うコスト(OSにイベントが飛ぶなど)、スレッドの切り替えに伴うコストなどがあり、CPUに対する負担が大きく、また特に、CPUの世界よりも圧倒的に遅いハードディスクへのアクセスなどの入出力処理(I/O)において、処理を待機している時間CPUを占領させることが非効率であるとも言えます。
そこでLockBit 2.0は、そうした観点が考慮された仕組みである「I/O完了ポート」を採用することで効率化を図っています。I/O完了ポートを利用することで、CPUの世界から見て時間の要する入出力処理(I/O)が完了するまでCPUリソースを他の処理のために一旦開放し、入出力処理(I/O)が完了したタイミングでスレッドプール(複数のスレッドを待機させつつ空いたスレッドに適宜処理させる仕組み)に処理依頼(キュー)を渡すという流れでCPUに処理を再開させることができ、CPUの占有を抑えることで効率的にCPUリソースを活用したマルチスレッドを実現できます。また、同時にアクティブとなるスレッドの数を指定することができるため、プロセッサ数以上のスレッドを動作させないといった制御が可能です。
このため、多数のI/Oを並行発生させることで高速化したい「ランサムウェア」というプログラムにとってI/O完了ポートの採用は効果的な選択であると言えるでしょう。
具体的には、ネイティブWindows APIであるNtCreateIoCompletion、NtSetInformationFile、ZwRemoveIoCompletion、ZwSetIoCompletionという関数群を呼び出すことで実現しています。
下図は、実際にLockBit 2.0がファイルを暗号化する際に使用するI/O完了ポートの利用流れを図示したものです。ファイルを暗号化する際の入出力処理(I/O)において、I/O完了ポートが一元管理することでCPUの処理が効率化されます(下図参照)。
図 38 I/O完了ポートを利用したLockBit 2.0のファイル暗号化処理の高速化の仕組み
つまり、I/O完了ポートの採用により、実際に暗号化処理の高速化が期待できるといえます。
また、LockBit 2.0はファイルの暗号化処理だけでなくその他の機能においてもマルチスレッドでサブルーチン化しておりそれぞれにおいてI/O完了ポートを活用した高速化を行なっています。
■「AES-NI」の活用による暗号化処理の高速化
インテルとAMDの一部のCPUには、AESの暗号処理を高速に行う為の「AES-NI」(Advanced Encryption Standard New Instructions)という仕組みが実装されています。AES-NI は、AESの暗号化/復号処理のために特別に用意された専用の命令セットをハードウェアに実装することで大幅な高速化が行われる機能です。
LockBit 2.0はファイルの暗号化にAESを使用するため、AES-NIを活用することでファイルの暗号化処理を高速化しています。
具体的には、LockBit 2.0はまず感染端末のCPUの種類を示す「CPUID」やCPUの製造ベンダーを示す「ベンダーID」を確認することで、AES-NIに対応したCPUであるかを確認します。さらにAES-NIをサポートしているCPUであるかどうかをチェックします。
これらのチェックの結果、AES-NIに対応していると判断した場合は、LockBit 2.0はAES-NI専用に用意された命令セットである「aesenc」命令を使用して暗号化します(下図参照)。
図 39 AES-NIを活用したLockBit 2.0のファイル暗号化処理の高速化
つまりこのAES-NIの利用により、実際に暗号化処理の高速化が期待できるといえます。
■ファイルの先頭部分のみの暗号化による高速化
LockBit 2.0は、暗号化する対象ファイルの中身全てを暗号化せず、ファイルの先頭部分4KBのみを暗号化することでファイルの暗号化を高速化しています。以下の図は、LockBit 2.0に暗号化される前のファイルと暗号化された後のファイルの先頭部分を比較した様子ですが、先頭4KBのみが暗号化されていることがわかります(下図参照)。
図 40 LockBit 2.0に暗号化される前後のファイルの先頭部分の比較
また別の例として1GBのファイルサイズを持つ巨大なファイルを用意し実験した結果が以下ですが、LockBit 2.0によって暗号化された後のファイルを確認すると、先頭4KBしか暗号化されていません(下図参照)。
図 41 LockBit 2.0により暗号化された1GBのファイルの状態
確かに、他のランサムウェアにおいて、多数の大きなサイズのファイルが暗号化されるようなシチュエーションでは、暗号化スピードが大幅に減速することをこれまでしばしば目にしてきました。Contiの弊社解析記事においても、ファイルの拡張子やサイズによって暗号化処理を分けていることが分かっており、ランサムウェアにとってこの減速が課題であることは明らかです。LockBit 2.0が採用したこの方法のように、ファイルの先頭4KBのみを暗号化するという割り切りは、暗号化スピードの向上の観点においては非常に効果的なアイデアであると感じます。
また、4KBとはいえ、プレーンテキストの種類のファイルでない限り、ファイルの先頭4KBを暗号化された場合は元の状態に復元することは非常に困難であり、結果的にファイル全体を暗号化された場合と同等の影響を被害組織に与えられる手口であるともいえます。
■LockBit 2.0のファイル暗号化ロジックの詳細
ここでは、LockBit 2.0が行うファイルの暗号化のロジックを詳細解説します。
LockBit 2.0のファイル暗号化のロジックを次の図にまとめましたので、図に沿って解説します(下図参照)。
まず、LockBit 2.0の攻撃者側は公開鍵暗号方式である「ECC」(楕円曲線暗号)鍵のペア(これを「マスターECC公開鍵」と「マスターECC秘密鍵」と呼びます)を生成し、「マスターECC秘密鍵」を攻撃者側の手元で管理しています。
そして、LockBit 2.0のEXE本体にこの「マスターECC公開鍵」がハードコードされています。
次に、LockBit 2.0は感染時、感染端末ごとに一意となる別のECC鍵のペア(これを「セッションECC公開鍵」と「セッションECC秘密鍵」と呼びます)をローカルで生成します(※)。
(※)なおLockBit 2.0はECC(楕円曲線暗号)の楕円曲線に「Curve25519」を使用しています。RSAより処理速度の早いECCを採用している点で高速化を狙った意図等が考えられます。
一方で、LockBit 2.0は暗号化対象のファイルを暗号化する際AES暗号で暗号化しますが、その際に使用されるAES鍵(暗号鍵&IV)はファイルごとに動的生成されます。つまり、AES鍵(暗号鍵&IV)はファイルごとに異なります。
ではファイルを暗号化する際のロジックを見ていきましょう。
LockBit 2.0はあるファイルを暗号化する際、そのファイルのフッタに、「セッションECC公開鍵」で暗号化した「AES鍵(暗号鍵&IV)」と、「マスターECC公開鍵」で暗号化した「セッションECC秘密鍵」を追記して書き込みます。
したがって、「マスターECC秘密鍵」があれば、対となる「マスターECC公開鍵」で暗号化された「セッションECC秘密鍵」が抽出でき、さらにその「セッションECC秘密鍵」があれば、対となる「セッションECC公開鍵」で暗号化された「AES鍵(暗号鍵&IV)」を取り出すことができます。
つまり、ファイルの復号は「マスターECC秘密鍵」を持った攻撃者のみが行えることを意味します。
図 42 LockBit 2.0の暗号化ロジック図
上図にも一部記載していますが、LockBit 2.0はセッション鍵のペア(「セッションECC公開鍵」と「セッションECC秘密鍵」)を生成した際にレジストリに書き込むことで記録します(下図参照)。
なお、レジストリに記録されるセッションECC秘密鍵は暗号化された状態で書き込まれ、メモリ上に生成されていたセッションECC秘密鍵はすぐに0xFFで上書きされ消去されます。
図 43 レジストリに書き込むセッション鍵のペア
この背景ですが、LockBit 2.0は起動した際、上記レジストリが存在した場合は前回の暗号化処理が途中であると判断し、これらのセッション鍵のペアを読み込むことで続きの暗号化に利用します。つまり、途中で端末がシャットダウンされるなどで中断された場合においても同じセッション鍵で暗号化が再開できるようにしていると考えられます(下図参照)。
図 44 起動時にセッション鍵を読み込む処理
なお、ファイルを暗号化する際のAES暗号鍵の作成方法ですが、BcryptGenRandom関数を使用し32バイトのランダムなバイト列を生成します。この時点で鍵長が32バイト=256ビットである可能性が考えられましたが、BcryptGenRandomを使用し生成した32バイトのランダムなバイト列のうち、最初の16バイトをIV(初期化ベクトル)として、残りの16バイトをAES暗号鍵として使用することが確認され、この時点で128ビットの鍵長であることがわかりました(下図参照)。
図 45 AES暗号鍵の作成方法
さらにファイルの暗号化に使用するAESのアルゴリズムにおいて、10個のラウンド定数による10回のラウンド処理が確認でき、これも鍵長が128ビット(128ビットはラウンド10回)であることを意味します。
また、前回暗号化した列との排他的論理和の処理が確認されるため、この特徴はCBCモードを意味します(後述)。暗号化するデータを16バイトごとに暗号化(異なる暗号化鍵で10回処理)し、それを256回繰り返します。この結果、16x256=4096バイト暗号化することとなり、前述したとおりファイルの先頭を4KBしか暗号化しないことを意味します。
初期化ベクトル(IV)としての利用部分と上記のラウンド処理が確認できたことから、暗号アルゴリズムは「AES128ビット」「CBCモード」であると断定できます(下図参照)。
図 46 ファイルの暗号化に使用するAESのアルゴリズム
なお、上記で「前回暗号化した列との排他的論理和の処理」がAES暗号におけるCBCモードの特徴であることに触れましたが、CBCモードの具体的な処理の流れは以下のようになります。暗号化するデータの最初の16バイトは、IV(初期化ベクトル)と暗号鍵が使用されますが、2ブロック目以降の16バイトは、1つ目の暗号化ブロックをIV(初期化ベクトル)の代わりに使用します(下図参照)。
このように前回暗号化した列を入力値とする排他的論理和の処理があれば、CBCモードと判別できる一つの特徴であるといえます。
図 47 AES暗号/CBCモードの仕組みと特徴
以上の暗号化処理により、LockBit 2.0が暗号化したファイルのフッタ構造は以下の図のようになります。512バイトあるフッタ全体のうち、後方に「セッションECC公開鍵」で暗号化した「AES鍵(暗号鍵&IV)」と、「マスターECC公開鍵」で暗号化した「セッションECC秘密鍵」を追記します(下図参照)。
図 48 LockBit 2.0に暗号化されたファイルのフッタの全体構造
上記図にも記載した通り、フッタの末尾にはランサムウェアの「検体」を識別するための8バイトのマーカーが記載されます。この8バイトの値はLockBit 2.0の本体(EXEファイル)のバイナリ部分にハードコードされており、検体に依存する値であるため、検体識別用に使用可能なデータであるといえます。使用した検体が特定できれば、攻撃者側で使用されているマスター鍵の識別が可能であると想定されます(下図参照)。
図 49 フッタの末尾に含まれる「検体」識別用マーカー
そしてもう一つ、フッタの末尾には「端末」を識別するための8バイトのマーカーが記載されます。この8バイトの値は、LockBit 2.0が作成したレジストリの「Public」に格納されたセッションECC公開鍵の先頭8バイトとなっており、セッションECC公開鍵はLockBit 2.0が最初に実行された際に生成されるものであるため、攻撃者側で端末を識別する際の目安として使用できるデータであるといえます(下図参照)。
図 50 フッタの末尾に含まれる「端末」識別用マーカー
なお、上記レジストリキーの命名規則は、LockBit 2.0の実行ファイルにハードコードされたバイナリ値から特定の場所をピンポイントで抽出したものが使用されています(下図参照)。
図 51 レジストリキーの命名規則
LockBit 2.0はこうしたファイルの暗号化を、GetDriveTypeW関数で書き込み可能と判別した全てのドライブ配下のファイルに対して実施していきます(下図参照)。
図 52 書き込み可能な全ドライブのチェック
なお、LockBit 2.0はネットワーク共有に設定されたリソース先もアクセスし暗号化する能力を持っています。
WNetOpenEnumW 関数およびWNetEnumResourceW関数を使用して感染端末からアクセス可能な全てのネットワーク共有を列挙し、発見されたネットワーク共有ごとに新しいスレッドを作成しリソース先に存在する全てのファイルに対し暗号化を行っていきます(下図参照)。また、WNetAddConnection2W関数を利用したネットワーク共有へのアクセスについても試みます。
図 53 ネットワークリソースの暗号化処理
■書き込みが禁止されているファイルの属性変更
LockBit 2.0はできるだけ多くのファイルを暗号化するために、「読み取り専用」属性がついているファイル(つまり書き込みが禁止されているファイル)を発見した場合、該当の属性を削除します。
図 54 読み取り専用属性が削除される様子
具体的には、個々のファイルを暗号化する際GetFileAttributes関数でファイルの属性をチェックし「読み取り専用」属性が設定されているファイルであると判断すると、SetFileAttributes関数に「0x80=FILE_ATTRIBUTE_NORMAL」の引数を渡し、属性が設定されていない状態に変更します。
これによりたとえ「読み取り専用」属性が付与され書き込みが禁止されているファイルであっても、書き込み可能な状態にしたうえで暗号化を行うことが可能となっています。
図 55 SetFileAttributes関数による属性変更の処理
■ファイルの暗号化から除外する対象
ファイルを暗号化する際、以下に挙げる「ファイル名」「フォルダ名」「拡張子」を暗号化対象から除外します。これらにはシステムが動作するために最低限必要なファイルやフォルダ、および攻撃者とコミュニケーションするためのブラウザなどが含まれています(下図参照)。
図 56 LockBit 2.0がファイルの暗号化から除外する対象
■LockBit 2.0に暗号化されたファイルのアイコン
LockBit 2.0は、以下のレジストリを改変することで「.lockbit」という拡張子(暗号化したファイルにつける拡張子)を持つファイルのデフォルトアイコンを、自身が作成したLockBit 2.0のデザインのアイコンに設定します。
図 57 アイコンファイルの作成と設定
そして、設定したアイコンをすぐにシステムに適用させるため、SHChangeNotify関数を呼び、システムのアイコン描画をリフレッシュさせます。
図 58 登録したアイコンのシステムへの即時反映の処理
その結果、LockBit 2.0に暗号化されたファイルは全てアイコンが以下のように変化します。
図 59 LockBit 2.0に暗号化されアイコンが変化したファイルの一覧
ただし、一部の検体において、アイコンが変化しないケースが存在することが複数の検体を解析することで明らかとなりました。そのため、LockBit 2.0の検体によっては、感染したアイコンが変化しない場合も存在します。
■LockBit 2.0のバグ(暗号化が途中で失敗するミス)
LockBit 2.0には、環境によってファイルの暗号化処理が途中で失敗してしまうバグを持つ検体が存在することが今回の調査の結果新たに判明しました。これは本検証時において最新のWindows Updateを適用した後のWindows 10で確認されており、LockBit 2.0がシステムに関わる特定の特殊フォルダへのアクセス権がないことで、該当のシステムフォルダへアクセスした時点でファイルの暗号化スレッドがクラッシュし無限ループに陥るか、LockBit 2.0のプロセス自体がクラッシュして終了します。
このシチュエーションに該当する環境では、ファイルが途中まで暗号化されているものの、LockBit 2.0の後半の挙動で行う処理である「デスクトップの壁紙変更」、「デスクトップ上の脅迫文(HTA)の作成」などが発生しません。
そのため、実際の被害組織の中にはデスクトップ壁紙がLockBit 2.0を示すわかりやすい画像に変更されず、また脅迫文も目が届くデスクトップなどに作成されないことから、LockBit 2.0という種類のランサムウェアに感染したこと自体を把握できていないケースがあるものと推測されます。
この場合、Cドライブ直下のアルファベット順で比較的前方にあるフォルダ内を確認し、ファイルの拡張子が「.lockbit」のものが存在するかどうかで、LockBit 2.0に感染し一部のファイルが暗号化されたかどうかを判別することが可能です。
■LockBit 2.0のバグ(多重暗号化を発生させるミス)
LockBit 2.0には、シチュエーションによってファイルを多重に暗号化してしまうバグを持つ検体が一部存在します。
LockBit 2.0は、暗号化済みのファイルかどうかをチェックする際、ファイル内部のフッタなどは確認せず単純に「.lockbit」という拡張子の有無でのみ判別しますが、ネットワーク共有フォルダなどネットワーク経由で暗号化を行う場合に限り、暗号化したファイルの拡張子を「.lockbit」に変更しないというバグを抱える検体が存在します。
そのため、感染端末が存在するローカルネットワーク上には、拡張子が「.lockbit」でないにも関わらず暗号化されたファイルが存在する可能性があり、状況によっては多重に暗号化される可能性があります(下図参照)。
図 60 ネットワーク越しで暗号化されたファイルの拡張子は変化しない
多重に暗号化されることを確かめるため、実際に次の検証を行いました。
以下はネットワーク共有したフォルダを持つ端末α上でLockBit 2.0に感染させた際の検証結果を図示したものです。端末α上の共有フォルダには「ファイルB」が配置しており、「ファイルB」はすでに他の端末β上の”バグを持つ検体”により暗号化されている状況となります。上記で述べた通り、バグを持つ検体によりネットワーク経由で暗号化された「ファイルB」は、暗号化されているものの拡張子が「.lockbit」ではない状態になっています。
この状況下で、端末αをLockBit 2.0に感染させたところ、「ファイルB」は想定通り2重に暗号化されることが確かめられました(下図参照)。
図 61 バグを持つ検体の動作
上記の検証結果を踏まえると、例えば以下のように、多数の感染端末からアクセス可能な共有フォルダが存在する状況下でバグを持つLockBit 2.0に感染した場合、3重4重と感染端末の数だけ多重に暗号化されてしまう可能性があるといえます(下図参照)。
図 62 LockBit 2.0に多重暗号化される可能性とシチュエーション
このバグを抱えるLockBit 2.0の個体に感染し、ファイルが多重に暗号化されてしまっていた場合、たとえ攻撃者から復号ツールを提供され復号しようとしても、実際は復号できないという状況が発生すると想定されます(復号ツールも拡張子で判別すると考えられるため)。
もちろん理論上、ファイルフッタの構造を都度判別し多重に暗号化されていることを想定して元のファイルになるまで繰り返し復号するツールを作れないわけではないですが、そこまで煩雑な復号ツールを作るのであれば、はじめから多重暗号化が発生しないようにランサムウェア側を実装したほうが現実的といえます。
つまり、ファイルが多重に暗号化されてしまうシチュエーションがあるというこの事実は、LockBit 2.0の攻撃者が想定しているとは考えられず、ファイルの復元が困難となる致命的なバグの一つであると考えられます。
ただし、比較的新しくビルドされたLockBit 2.0において、このバグが修正されている個体も存在することをその後確認しました。以下の図の通り、バグが修正された個体ではネットワーク経由で暗号化した場合も正常にファイルの拡張子を「.lockbit」に変更するように修正されているため、多重に暗号化されるという上記問題が解消されています。
図 63 バグが修正された検体の動作
バグを持つ個体とバグが修正された個体はビルド日時が比較的近いため、現時点でLockBit 2.0に攻撃されている被害組織の中には、多重暗号化により復元を困難とさせてしまうバグを持つ個体が実際に使用されているケースも少なくないものと想定され、注意が必要です。
■LockBit 2.0のバグ(「.lock」ファイルのチェックミス)
LockBit 2.0は多重暗号化を防ぐため、プロセスの起動中はドライブ直下に「.lock」というファイルを作成し常に開いた状態にします。「.lock」のファイル名部分の文字列は「セッションECC鍵」のペアを記録したレジストリキー名の最初の4バイトが使用されます。
LockBit 2.0は起動時に該当の「.lock」ファイルがCreateFileで開けるか(ハンドルがとれるか)をチェックし、直前のエラーコードを「0xB7」(ERROR_ALREADY_EXISTS)と比較、該当した場合は自身を終了させます。
もしエラーコードが「0xB7」に該当しなかった場合は、処理を継続し「.lock」ファイルのハンドルを動作中は常に開いたままの状態にします。
ローカルにおける多重感染防止は前述のとおりミューテックスで実現されていることから、この「.lock」ファイルの役割は該当ドライブがネットワーク越しで暗号化される場合を想定した多重暗号化防止の役割を持つことが想定されます。つまり、「.lockファイルのハンドルが開けない」=「すでに該当の端末(ドライブ)では他のLockBit 2.0が動作し暗号化している最中である」と判断し、ファイルが多重に暗号化されることを防ぎたいのだろうと考えられます(下図参照)。
図 64 「.lock」ファイルのハンドルオープン処理
しかし、今回の解析の結果、ここにもバグが存在することが発見されました。
LockBit 2.0は直前のエラーコードを「0xB7」(ERROR_ALREADY_EXISTS)と比較していますが、「.lock」ファイルのハンドルが開かれた状態で感染させると、実際にはOSから「0x50」(ERROR_FILE_EXISTS)というエラーコードが応答されます。そのため、実検証では「.lock」ファイルのハンドルが開かれた状態であっても処理は止まらず暗号化は継続されてしまいました。
また、一部のLockBit 2.0には前述した通り、ネットワーク越しで暗号化したファイルの拡張子を「.lockbit」に変更しないというバグも存在します。
つまり、これらのバグが組み合わさることで、他の端末上のLockBit 2.0からネットワーク越しで並行して暗号化が行われた場合、結果的に排他制御ができず、共有フォルダのみでなく共有化された各端末のドライブにおいても多重の暗号化が発生してしまう可能性があることを意味します。
この結果はコードの実装を見る限り開発者にとって想定外である可能性が高く、「多重暗号化を防ぎたい」という本来の意図を推し量るとLockBit 2.0の開発者はこの比較対象のエラーコードを「0xB7」ではなく「0x50」という値にしたかった可能性があります(下図参照)。
図 65 LockBit 2.0のエラーコードの比較処理に含まれるバグ
■ドメインコントローラでの動作
LockBit 2.0にはドメインコントローラで行う特別な挙動が存在します。以降ではその挙動を解説します。
LockBit 2.0は感染時、GetComputerName関数を使用してコンピュータ名を取得したのち、NetGetDCName関数によりプライマリドメインコントローラの名前を取得できるか確かめます。もしプライマリドメインコントローラの名前が取得できた場合、その名前の先頭から”¥¥”(バックスラッシュ)を2つ削除した文字列と、先ほど取得したコンピュータ名の文字列を比較することで、感染した端末がドメインコントローラであるかどうかをチェックします(下図参照)。
図 66 ドメインコントローラの名前を取得し判別する処理
ドメインコントローラであると判断した場合、以下の処理を継続します。
LockBit 2.0は自身のプロセスの実行アカウントが「DomainAdmins」(ドメイン管理者グループ)に属しているかどうかをチェックします。
具体的には次の方法で行います。
ZwOpenProcessToken関数にTOKEN_QUERY(0x8)アクセス権を渡すことで現在のプロセスのトークンを取得したのちNtQueryInformationToken関数で現在のSIDを取得、取得した現在のSIDから、GetWindowsAccountDomainSid関数を使用してドメインSIDを取得します(下図参照)。
図 67 ドメインSIDを取得する処理
取得したドメインSIDとWellKnownSidTypeとして知られる「WinAccountDomainAdminsSid」(0x29=41)という値をCreateWellKnownSid関数に渡すことでDomainAdmins(ドメイン管理者グループ)のSIDを取得(作成)、最後にCheckTokenMembership関数を利用して、呼び出し元のトークン内でDomainAdmins(ドメイン管理者グループ)のSIDが有効になっているかどうかをチェックします(下図参照)。
(※もしDomainAdminsに属していないと判断すると「Don’t have admin rights」という文字列をログ用にメモリ上に作成します)
図 68 DomainAdminsに属しているかどうかのチェック処理
DomainAdminsに属していると判断すると、LockBit 2.0はDomainAdminsのアカウント名(ドメイン管理者)を取得します。
具体的には次の方法で行います。
先ほどと同じくZwOpenProcessToken関数にTOKEN_QUERY(0x8)アクセス権を渡すことで現在のプロセスのトークンを再度取得したのち、NtQueryInformationToken関数で現在のSIDを取得、LookupAccountSidW関数を使用し、取得したSIDに対するアカウント名(Administrator)とアカウント名が見つかったドメイン名(例:ADTEST)を取得します(下図参照)。
図 69 DomainAdminsのアカウント名とドメイン名を取得する処理
以上により、ドメインコントローラであることの確認およびドメインコントローラの情報を取得できた後、LockBit 2.0は、グループポリシーを作成し、ドメイン配下の全てのクライアント端末にランサムウェアを配布する挙動を行います。
このグループポリシーによる自身の拡散という仕組みは、暴露系ランサムウェアとしては史上初の新機能であると考えられます。
LockBit 2.0がドメイン内の各端末に配布するグループポリシーは以下に挙げる6つです(下図参照)。
図 70 LockBit 2.0が配布するグループポリシー
それぞれについて、以降で詳細解説していきます。
LockBit 2.0は、まずCreateFileでグループポリシー設定ファイルである「GPT.ini」を作成します(下図参照)。
図 71 LockBit 2.0により作成される「GPT.INI」
続いてCreateGPOLink関数により、指定したGPO(グループポリシーオブジェクト)とOU(Active Directoryでアカウント管理の最小単位となるアカウント・コンピュータ・リソースの集合)をリンクさせグループポリシーの適応先を設定(下図参照)、ADSI関数(Active Directory サービスインターフェイス)であるADSGetObject関数を使用した上でグループポリシーの属性(gPCMachineExtensionNamesやgPCUserExtensionNamesなどの拡張属性)の設定を行います。ADSGetObject関数はアプリケーションがActive Directory(AD)にアクセスするための専用APIであり、この関数によりAD内のオブジェクトの検索・閲覧・編集・削除などが実行できるようになります。
図 72 グループポリシーをOUにリンクさせる処理
その後、ドメイン内のクライアントに配布するための複数のグループポリシーファイルを作成していきます。
まずは、「LockBit 2.0本体をドメイン配下に一斉配布するグループポリシー」を作成します。
あらかじめ、ドメインコントローラにあるSYSVOLフォルダ内scriptsフォルダに自身のコピーを作成した上で、「Files.xml」というグループポリシー設定ファイル(xml)を作成します。
作成された「Files.xml」には、ドメインコントローラのscriptsフォルダに作成したLockBit 2.0の本体EXEを、ドメイン配下の各クライアント端末のデスクトップ上にコピーするよう指示するポリシーが記述されています(下図参照)。
図 73 LockBit 2.0の本体をドメイン配下に一斉配布するポリシー
上記のグループポリシーをより分かりやすく表示し直したものが以下です(下図参照)。
つまり、この「File.xml」のグループポリシーが適用された瞬間に、ドメイン配下の全ての端末のデスクトップにLockBit 2.0のEXEファイルがコピーされます。
図 74 ドメイン配下端末へのLockBit 2.0のEXEファイルの一斉配布が指定されたポリシー
続いて、「ScheduledTasks.xml」というグループポリシー設定ファイル(xml)を作成します。
この「ScheduledTasks.xml」には、2つのスケジュールタスクがまとめて記述されています。「ScheduledTasks.xml」の前方に「①プロセスの強制終了」に関するスケジュールタスクが、「ScheduledTasks.xml」の後方に「②EXEファイルの実行」に関するスケジュールタスクが設定されています。
図 75 LockBit 2.0が作成する「ScheduledTasks.xml」
上記のグループポリシーをより分かりやすく表示し直したものが以下です。
「プロセスの強制終了」に関するスケジュールタスクには、インシデント調査ツールや、データベースアプリケーション、サーバアプリケーションなど、暗号化の邪魔となるプロセスの名前が並び、taskkillコマンドにより即時強制終了されるよう設定されていることがわかります(下図参照)。
図 76 指定した多数のプロセスを強制終了させるスケジュールタスク
また、「ScheduledTasks.xml」にある「EXEファイルの実行」に関するスケジュールタスクには、さきほどの「File.xml」によってあらかじめ作成されたデスクトップ上のEXEファイル(LockBit 2.0本体のコピー)を即時実行するルールが記述されています(下図参照)。
図 77 配布したEXEを実行させるためのスケジュールタスク
以下は、上記のグループポリシーにより、クライアント端末上に実際に配布されたスケジュールタスクの中身ですが、上記で解説したものと同じものがスケジュールタスクとして設定されていることがわかります(下図参照)。
図 78 実際にクライアント端末側に作成されたスケジュールタスク
また、クライアント端末上のタスクスケジューラから確認したものが以下ですが、「プログラムの開始」としてデスクトップ上のLockBit 2.0の本体のEXEパスが設定されていることがわかります(下図参照)。
図 79 タスクスケジューラで確認した様子
続いて、「Services.xml」と「NetworkShares.xml」という2つのグループポリシー設定ファイル(xml)を作成します(下図参照)。
図 80 作成されるサービスとネットワーク共有に関するポリシー設定ファイル
上記で作成された「Services.xml」には、クライアント端末上の複数のサービス(暗号化の邪魔となるサービスなど)を停止させる処理が記述されています(下図参照)。
図 81 LockBit 2.0が作成した「Services.xml」
もう一つ作成される「NetworkShares.xml」には、クライアント端末上の全てのドライブドライブをネットワーク共有に設定するためのポリシーが記述されています(下図参照)。この結果、他の感染端末からネットワーク越しに、それらのネットワーク共有されたドライブ内のファイルが暗号化されてしまう状況に陥ります。
図 82 LockBit 2.0が作成した「NetworkShares.xml」
続いて、「Registry.pol」というグループポリシーファイルを作成します(下図参照)。
この「Registry.pol」には、Windows Defenderを無効化するための複数のレジストリ設定が記述されています。このポリシーが配布され適用されることにより、ドメイン配下の全てのクライアント端末上でWindows Defenderが無効化される可能性があります。
図 83 LockBit 2.0が作成した「Registry.pol」
そして最後に、LockBit 2.0はPowerShellコマンドにより、強制的にグループポリシーをドメイン配下の全端末へ一斉配布します(下図参照)。
図 84 PowerShellコマンドによりグループポリシーが一斉配布される様子
上記のPowerShellのコマンドは以下のような構成となっており、「指定したOUに存在する全ての端末を取得」し、見つかった各端末に「グループポリシーを即時強制適用させる」コマンドであることがわかります(下図参照)。
図 85 グループポリシーを一斉配布させるPowerShellコマンドの構成
上記のPowerShellが実行された瞬間に、ドメインコントローラ上ではコマンドプロンプトが開き、グループポリシーが更新される旨のメッセージが表示されます。またその直後、ドメイン配下にあるクライアント端末上でも同様にグループポリシーが更新されるプロンプトが表示されます(下図参照)。
(この際、LockBit 2.0は自身の内部で「Run on all domain(waiting 1 min)」というログをメモリ上に展開します。)
図 86 ドメインコントローラおよびクライアント端末上でグループポリシーが更新される様子
上記のポリシーが強制的に適用された瞬間、クライアント端末上のデスクトップにはコピーされたLockBit 2.0のEXEファイルが作成され実行されます(下図参照)。
図 87 グループポリシーの適用後デスクトップ上に作成されるEXE(LockBit 2.0の本体)
グループポリシーによる不正処理の一斉展開の仕組みは以上となります。
ドメインコントローラでのグループポリシーを利用したランサムウェアの展開の様子を動画に収録し、以下のYouTubeで公開していますので併せてご覧ください↓
■LockBit 2.0のバグ(作成されたタスクのミス)
なお、「ScheduledTasks.xml」に記載されていたデスクトップ上のEXEファイルを実行させるタスクですが、調査の結果、複数の検体において、グループポリシーで配布されたEXEの実行が、クライアント端末側で失敗する個体が存在することを確認しました。
LockBit 2.0は、各クライアント端末に「ドメイン管理者アカウント」(Administrator)が前提条件として「ログオンしている必要がある」タスクを設定してしまっており、現実的にはこの起動条件が満たさせることは通常はあまり起こりえません。
ただし、このタスク内の権限設定が「ドメイン管理者アカウント」(Administrator)ではなく「SYSTEMアカウント」または「ユーザアカウント」が指定されていれば、攻撃者の意図する状況が成功していたものと考えらえます。実際に、LockBit 2.0が作成した「ScheduledTasks.xml」の中の権限設定の記述を手動で書き換え、「SYSTEMアカウント」を指定したものを配布するようにした場合、ポリシーが配布された直後にLockBit 2.0がタスクにより実行され自動感染することが確認されました(下図参照)。
図 88 LockBit 2.0のスケジュールタスクに発見されたバグと想定される設定
ただし、一連の調査を進めた後、新しく出現したLockBit 2.0の検体において、このバグが修正されている個体を発見しました。
古い検体と新しい検体によって作成された「ScheduledTasks.xml」の記述をそれぞれ比較すると、上記でバグであると推測していた部分の記述が以下のようにしっかりとピンポイントで修正されていることがわかります(下図参照)。
そのため、この新しいLockBit 2.0の個体においては、ドメイン配下のクライアント端末上でスケジュールタスクによってEXEが正常に実行され、自動的に感染が発生することも確認されました。
図 89 新しいLockBit 2.0の検体でタスクのバグが修正されていることが確認された様子
このように、LockBit 2.0の開発者はバグを随時修正しながら攻撃に使用していることが明確に確認できたため、検体の挙動における個体差(揺れ)があり、挙動にバグがあったとしても既に修正されたバージョンが出回っている可能性は高く、安心できる状況ではありません。
■デスクトップ壁紙の変更
LockBit 2.0は、感染した端末のデスクトップの壁紙を脅迫文が記載されたものに変更する挙動がありますが、他のランサムウェアにみられるように単純にあらかじめ用意された画像を取り出して使用するのではなく、感染端末のディスプレイサイズに合わせたデザインとなるように、わざわざ「Windows GDI+」(Graphics Device Interface)を用いて、一から画像を描画して壁紙を動的に生成します(下図参照)。
図 90 壁紙を一から描画して生成
以下のように壁紙に表示させる文字列も復号して用意し、Windows GDI+のAPIを用いてテキストとして描画します(下図参照)。
図 91 復号したテキスト文字列(脅迫文)を生成した画像に描画する処理
LockBit 2.0は一時フォルダ(%temp%)内に、上記の流れで生成した画像を一時ファイルとして作成します(下図参照)。
図 92 壁紙に設定する画像の出力
作成した画像のフルパスを壁紙の設定に関わるレジストリ値を書き換えることで設定します。またその際、ディスプレイサイズに合わせて壁紙が見やすくなるように、いくつかの設定を行います(下図参照)。
図 93 生成した画像パスなどをレジストリに設定する処理
上記の操作により、設定された壁紙に関わるレジストリ設定は以下となります(下図参照)。
図 94 LockBit 2.0により改変された壁紙に関わるレジストリ設定
上記のレジストリを設定しただけではすぐに壁紙の変更がシステムに反映されないため、LockBit 2.0はSystemParametersInfoW関数を使用することで即時壁紙の変更を反映させます(下図参照)。
図 95 SystemParametersInfoWによる壁紙変更の即時反映
以上により、動的に生成した壁紙がデスクトップに反映されます。
なお、このデスクトップの壁紙には、インサイダー(内部者)を募集する記載が書き込まれるタイプの検体が存在します(この壁紙を設定しない検体も存在します)。
インサイダーを募集する壁紙には「あなたも数百万ドルを稼ぎませんか?」と記載されており、この壁紙を見た人物に、どこかの会社の内部情報を知っている場合は攻撃者に提供すれば金銭を得られることを示唆する文章が並びます(下図参照)。
こうしてインサイダーを募集する壁紙を設定するという挙動は他のランサムウェアに見られないため、LockBit 2.0が持つ新規性の高い挙動の一つといえます。
これまでのRaaSにおいてはアフィリエイトなどが初期アクセスブローカーからの認証情報の購入に頼っていた側面もありますが、新しい攻撃手口のアイデアとしてインサイダーからの情報提供を初期アクセスのきっかけにしようとする企みが垣間見えます。この企みが成功した場合、攻撃者の侵入経路として非常に効果的な手段を提供する新たな脅威となる可能性があります。
図 96 インサイダーを募集する壁紙が設定された感染端末の様子
■脅迫文が記述されたテキストファイルの作成
LockBit 2.0は、暗号化したファイルが含まれるすべてのフォルダの中に、脅迫文が記述された「Restore-My-Files.txt」というテキストファイルを作成します。この脅迫文には、組織内のデータが盗まれてすでに暗号化されている旨と、身代金を支払わなければデータがダークウェブ上に暴露されるリスクがある点などが記述されています。
図 97 全てのフォルダに作成されるテキスト形式の脅迫文
■HTAファイルによる脅迫文の表示
LockBit 2.0は、脅迫文を提示する別の方法として、デスクトップにHTMLで記述されたアプリケーションであるHTA(HTML Application)ファイルを作成し利用します(下図参照)。このHTAファイルには”ファイルを復元するには攻撃者へコンタクトを取るしか道はない”という趣旨のコメントと、どのような手順でコンタクトを取れば良いかが記載されています。
図 98 デスクトップに作成され脅迫文を表示するHTAファイル
また、作成したHTAファイルが端末の再起動時に常に自動的に起動するようにレジストリのRunキーに設定します(下図参照)。
図 99 HTAファイルを自動起動に関わるレジストリに設定する様子
続いて、「.lockbit」という拡張子を持つファイルをユーザが開いた場合、該当のHTAファイルが自動的に開くようにレジストリに設定を行います(下図参照)。
図 100 暗号化されたファイルを開くとHTAファイルが開くように設定されたレジストリ
そして、ShellExecuteWでHTAファイルを起動し表示させます(下図参照)。
図 101 感染後にHTAファイルを自動的に開く処理
これにより、以下のように自動的にHTAファイルが開かれます。画面サイズいっぱいに開かれるため、一見デスクトップの壁紙に見えますが、これはHTAが表示するウインドウです(下図参照)。なお、このウインドウは[Alt]+[F4]キーで閉じることができます。
図 102 感染後にモニター上に表示されるHTAファイルが生成するウインドウ
■プリンタを利用した脅迫文の物理的な印刷(約一万枚)
LockBit 2.0の特徴的な挙動の一つが、プリンタを利用した脅迫文の物理的な「印刷」です。
印刷を行うランサムウェアの出現は初めてではなくEgregorという別のランサムウェアが過去に実装してはいたものの、ランサムウェア全体から見るとほとんど存在せず非常に珍しい挙動といえます。
LockBit 2.0は「あなたのデータは盗まれた上に暗号化された。身代金を支払わなければデータが公開される」という文言が並んだ脅迫文を、感染端末からアクセス可能なネットワークプリンタを含む全てのプリンタで印刷しようとします。
図 103 LockBit 2.0に感染し脅迫文をプリンタで印刷される様子
<余談>
以下の記事で解説していくように、ランサムウェアの解析結果を通した理論上においては、プリンタの印刷機能が特定され印刷機能を持っていることが把握できたのですが、実際にプリンタで印刷されるのか実検証したところ、想像以上に行き詰りました。
また、こうした物理的なデバイスに絡む挙動は仮想解析環境+仮想プリンタなどでは実機とは異なった結果となる場合があり、実際に「仮想プリンタ」を用いた検証ではLockBit 2.0に感染させても印刷が発生しませんでした。
そのため、プリンタの実機を使用した検証を行うことにしました。プリンタ実機を感染端末に接続すればすんなり印刷されるものと想定していましたが実際には印刷が発生せず、複数台のプリンタを用いての数日間をまたいだ長い検証の結果、LockBit 2.0が送信する印刷データに対応しているプリンタ実機と、対応していないプリンタ実機があるという結論に達しました。
他社の解析記事においてはLockBit 2.0の印刷機能に触れているもの自体が少ないですが、記載されている場合も「LockBit 2.0は脅迫文を印刷する」という程度にしか触れられておらず、実際には印刷されるプリンタとされないプリンタがあるという新たな事実が今回の検証を通して判明したことになります。
静的解析でコードを見ただけでは「印刷する」という挙動は判るものの、印刷されないプリンタが存在するという事実は実際に実機を用いて検証しないとわかり得ない結果であるといえます。以上の検証結果から、実際の被害組織においても印刷が発生した環境と、印刷が発生しなかった環境があるものと考えられます。
LockBit 2.0は、プリンタ関連のAPIを使用するため、それらのAPIを提供する「Winspool.drv」をロードします(下図参照)。
図 104 Winspool.drvをロードする処理の一部
その後、EnumPrinters関数で感染端末からアクセス可能な全てのプリンタを列挙し取得します(下図参照)。
図 105 感染端末上で利用可能な全てのプリンタを列挙する処理
見つかった全てのプリンタに対し、それぞれOpenPrinter関数で開き操作可能な状態にした上で、StartDocPrinter関数でページの印刷を通知します(下図参照)。
図 106 見つかったプリンタを開き印刷するページを通知する処理
その後、プリンタで印刷するための脅迫文の文字列を復号しメモリ上に展開します(下図参照)。
図 107 全てのプリンタに印刷させる脅迫文の文字列を復号する処理
そして、WritePrinter関数で指定したプリンタに上記のデータを送信し印刷を指示します(下図参照)。
図 108 指定したプリンタにデータを送る処理
1ページ分のデータの送信が完了したのち、EndDocPrinter関数で指定されたプリンタの印刷ジョブを終了した上で、ClosePrinter関数でプリンタオブジェクトを閉じます(下図参照)。
図 109 印刷終了の処理
以上が1ページ分の処理ですが、LockBit 2.0はこの処理を約1万枚(9,999ページ)分繰り返します(下図参照)。
図 110 約一万枚印刷されるようにこれまで解説した一連の印刷処理を繰り返す
つまり、プリンタ一台当たり9,999枚印刷するよう指定されており、この操作を感染端末からアクセス可能なネットワークプリンタを含む全てのプリンタに対して行うため、途中でプリンタを急いで停止しない限り、延々と大量に脅迫文が印刷され続けてしまうことになります。
従来のランサムウェアが行ってきたようにモニター上で提示する脅迫文だけでなく、物理的なデバイスを用いて脅迫するというこの手法は、やはり被害組織に与える恐怖やインパクトはより大きいものになるだろうと想像します。
LockBit 2.0が実機プリンタで脅迫文を印刷する様子は動画に収録し、以下のYouTubeで公開していますので併せてご覧ください↓
■インシデント対応や復旧作業を妨害する処理
LockBit 2.0は以下のコマンドをcmdの引数として実行することで、システムのバックアップ(ボリュームシャドウコピー)や、イベントログを削除し、インシデント対応や復旧作業を妨害する処理を行います。
なお、以下のうち上から4つ目のコマンドには実効性がありませんが、他の一部のランサムウェアやマルウェアでも同コマンドが確認されているため、開発者が参照した際に記述ミスのままコピーして使用しているものと推測されます。
図 111 LockBit 2.0がインシデント対応や復旧作業を妨害する処理
■フォレンジックを意識した自身の抹消処理
LockBit 2.0は全ての活動が終了すると、自身のEXEファイルを削除しますが、その際、単純な削除(Delete)ではなく、LockBit 2.0のEXEファイルが存在するハードディスクの位置の先頭から512KB分のハードディスク領域を直接0で埋め尽くすことでファイルを破損させ完全に破壊した上で削除します。具体的には、fsutilコマンドを使用し「Data offset」と「length」によってサイズ範囲を指定した上で0埋めした後Delコマンドによってファイルを削除します。(※なお、これらのコマンド処理は外部プロセスであるcmd.exeに依頼するため、実際にはLockBit 2.0のプロセスが終了した後で実施されます)
このことから、フォレンジック調査時にランサムウェア本体が正常に復元されないよう意識し、念入りに自身を抹消していることがわかります(以下図参照)。
図 112 ハードディスク上から自身のファイルの一部を破壊した上で消去するLockBit 2.0
加えて、上記のDelコマンドが失敗した場合に備え、Windowsの再起動時に自動的にOSによってLockBit 2.0のEXEファイルが削除されるよう、システムに予約設定を行います。具体的には、MoveFileExW関数にMOVEFILE_DELAY_UNTIL_REBOOTの引数を渡すことでシステムに予約を行います(以下図参照)。
図 113 システム起動時に自動的にOSによって削除されるように設定するLockBit 2.0
これらの2つの方法で執拗に、不要になった自身のEXEを抹消しようとするランサムウェアは珍しい部類であるといえ、復旧作業でランサムウェアを入手させないように入念な隠蔽工作を行おうとする意図が感じられます。
これら一連の処理を行った後、LockBit 2.0はExitProecssにより自身の処理を終了させます(以下図参照)。
図 114 全ての処理を終えた後に自身を終了する処理
LockBit 2.0の挙動は以上となります。
以降では、LockBit 2.0が実装しているその他の機能をご紹介します。
■D&D(drag and drop)に対応
LockBit 2.0のEXEファイルは、ファイルやフォルダのD&D(drag and drop)に対応しています。この点に触れた解析記事は確認した範囲においては存在せず、世界的にも知られていないようです。
LockBit 2.0は起動した際、CommandLineToArgW関数を使用し実行引数の数が「2」であるかどうかをチェックします。引数の数が「2」である場合はD&D(drag and drop)の処理に遷移し、指定されたファイルまたは指定されたフォルダ内にあるすべてのファイルに対する暗号化処理のみを行います(以下図参照)。
つまり、LockBit 2.0は、自動的に動作するランサムウェアとしてだけでなく、攻撃者が指定した対象(ファイルやフォルダ)のみを暗号化したい、というシチュエーションでも活用できるよう、「暗号化ツール」として利用できるようにも開発されていることがわかります。
図 115 D&Dに対応したLockBit 2.0のEXE本体
■LockBit 2.0が隠し持つGUI(隠しウインドウ)の発見
冒頭の表層解析で解説した通り、LockBit 2.0のEXEがGUIアプリケーションとしてビルドされていたのにも関わらず、一般的な表層解析の範囲ではGUIコンポーネントなどのリソースデータが確認できなかったことが筆者は気がかりになっており、その観点でLockBit 2.0の起動中のプロセスを念入りに調査しました。
その結果、起動中のプロセス内には表層解析では見えなかったリソースが出現しており、buttonやListViewなどのGUIコンポーネントを持った非表示の”隠しウインドウ“が作成されていることを発見しました。隠しウインドウは「LockBit 2.0 Ransom」という文字列を持っており、「LockBit_2_0_Ransom」というウインドウクラスを持つことがわかります(以下図参照)。
図 116 LockBit 2.0のプロセスに隠されたGUIコンポーネントの発見
また、該当の隠しウインドウの状態を調べるとStyleが「4CF0000」(hidden,enabled)となっていることから、ウインドウの作成はされているが非表示になっているということがわかります(以下図参照)。
図 117 ウインドウが隠し状態に設定されている様子
この結果から改めて静的解析したところ、確かにCreateWindowExW関数でウインドウを生成している処理が確認されました(以下図参照)。
図 118 LockBit 2.0がウインドウを生成する処理
また、作成したウインドウをShowWindow関数で表示する際、引数として非表示を意味する「0」が渡されていることも確認できました(以下図参照)。つまり、ウインドウが生成されてはいるものの非表示になっているという先ほどの動的解析結果と一致することがわかります。
図 119 生成したウインドウを非表示状態で表示させる処理
そのため、非表示となっている隠しウインドウを無理やり表示させるようLockBit 2.0ランサムウェアのバイナリを変更したところ、これまで世界的にも公に知られていなかったLockBit 2.0の隠しウインドウが表示され姿を現しました(以下図参照)。
表示されたウインドウは、複数のタブを持ちGUIコンポーネントが整然と並ぶグラフィカルなインターフェイスとなっており、感染端末上のドライブ一覧や現在暗号中の状況がリアルタイムに表示される様子がわかります。また、右クリックメニューも用意され、暗号化処理の停止や再開などが選択できるようになっています。
図 120 姿を現したLockBit 2.0のGUI (本来隠されたウインドウ)
以下は、「Log」タブを表示させた様子ですが、動作に関わる細かなログがリアルタイムに出力されていきます。
(これらのログ文字列は、該当する各処理の中で随時にメモリ上に展開されSendMessage関数で送信されます)
図 121 LockBit 2.0のGUIにおけるLogタブの様子
先ほど解説した通り、ShowWindow関数の引数は変数ではなく0で固定化(ハードコード)されているため、それはLockBit 2.0のEXEファイルがコンパイルされた後に該当の値を動的に変更する仕組みを持たない可能性が高いということを意味します。つまり、RaaS(Ransomware as a Service)の中で、アフィリエイトに提供されるLockBit 2.0のバイナリのビルド画面にこのGUIの表示に関わるオプションが用意されていると推測され、画面の表示/非表示があらかじめアフィリエイトによって設定された上でビルド(コンパイル)されているものと考えられます。
攻撃者がランサムウェアを被害組織内に展開し感染させる際は基本的にGUI画面が表示されるべきではないため、このGUIオプションが有効になったバイナリが出回ることはほとんどないと想定され、そうした背景がLockBit 2.0の隠しウインドウの存在が一般的に知られていない理由の一つであるといえます。
以上、今回弊社の解析により公で初めて明らかとなったLockBit 2.0のGUIが実際に動作する様子は、動画で収録し以下のYouTubeにて公開します↓。
その他、ドメインコントローラでのグループポリシーを利用したランサムウェアの展開の様子や、プリンタによる印刷動画についても、それぞれ動画に収録し、以下のYouTubeで公開していますので併せてご覧ください。
<LockBit 2.0がグループポリシーで自身を拡散させる様子↓>
<LockBit 2.0がプリンタで脅迫文を印刷する様子↓>
※動画がお役に立てた際はぜひ「いいね!」、「フォロー」をお願いします。
※本記事で解析に使用した全てのLockBit 2.0のハッシュ値:
0545f842ca2eb77bcac0fd17d6d0a8c607d7dbc8669709f3096e5c1828e1c049
0906a0b27f59b6db2a2451a0e0aabf292818e32ddd5404d08bf49c601a466744
4bb152c96ba9e25f293bbc03c607918a4452231087053a8cb1a8accb1acc92fd
4edbf2358a9820e030136dc76126c20cc38159df0d8d7b13d30b1c9351e8b277
bcbb1e388759eea5c1fbb4f35c29b6f66f3f4ca4c715bab35c8fc56dcf3fa621
717585e9605ac2a971b7c7537e6e311bab9db02ecc6451e0efada9b2ff38b474
73406e0e7882addf0f810d3bc0e386fd5fd2dd441c895095f4125bb236ae7345
a7591e4a248c04547579f014c94d7d30aa16a01bb2a25b77df36e30a198df108
acad2d9b291b5a9662aa1469f96995dc547a45e391af9c7fa24f5921b0128b2c
b3faf5d8cbc3c75d4c3897851fdaf8d7a4bd774966b4c25e0e4617546109aed5
d089d57b8b2b32ee9816338e96680127babc5d08a03150740a8459c29ab3ba78
f32e9fb8b1ea73f0a71f3edaebb7f2b242e72d2a4826d6b2744ad3d830671202
3cbdd9250d50cf9e5c9aa6c0ade4dde2995e1319e96a160ba6730e063e86f5bc
6edbe66b81f7168fa9d4b087c28409d6dfc98d560760369da4ed4ca2262abd33
おすすめ記事