はじめに
はじめまして、社会情報研究所NTT-CERTの田中です。先月下旬である2022/9/21に、ランサムウェアLockBit3.0のBuilderのコードが流出しました。Builderを使うと、Encryptor(ランサムウェア本体)及びDecryptor(復号ツール)が簡単に作成できるため、攻撃者に悪用される恐れがあります。ランサムウェアによる被害は、IPA「情報セキュリティ10大脅威 2022」で1位となりましたが、現在でも脅威は拡大しています。本記事では、LockBit3.0 Builderを用いて生成されたEncryptorを解析しその特徴を示します。
エクゼクティブサマリ
LockBit3.0Encryptorは、呼び出すWindowsAPI名を隠すために、ヒープ領域に、ユーザコードとAPIを紐付けるためのジャンプコードを用意することが解析でわかりました。ジャンプコードは、シフト演算等で難読化がなされ、5パターンのバリエーションでランダムに生成されます。また起動ごとに各APIに対するジャンプコードは異なる作りになっています。
LockBit3.0Encryptorのその他の特徴として、擬似乱数生成にx86ネイティブ命令を使用したり、 MMXx86のマルチメディアレジスタを用いており、作成者の開発環境の特性が現れているのかもしれません。また暗号化の動作以外にもマルチスレッドを多用しています。
LockBit3.0 Builder
BuilderのリソースセクションにEncryptorとDecryptorが格納されていることがわかります。この記事でも解説されています。

Builderにより作成されたEncryptorを起動してしばらくすると、暗号化が行われ脅迫文が書かれる壁紙となります。

この状態で、Decryptorを配置し、起動すると、GUIダイアログが出て、押下することで、すべてのファイルの暗号化の解除され、壁紙も戻ります。

Lockbit3 Encryptor の解析
1. 解析のポイント
一般に、マルウェアは、様々な解析妨害の仕組みが入っています。Lockbit3 Encryptorは、比較的珍しい手法が取り入れられていましたので、その点にフォーカスして解析していきます。
尚、図に登場する、関数名やコメントは、実際にデバッガで動作を確認して、解析が終わって後からつけたものになります。ユーザコードをスネークケース表記、APIをキャメルケース表記としています。 (例 sub_40639C -> s_40639C_prepare_heap)
2. ファイルの表層解析
Encryptor(以下検体と呼びます)のセクション情報を調べます。実行可能フラグがついているセクションは、セクション名:.text及び.itextであることがわかります。エントリーポイントは0x1946Fであり、.itextセクションにあることがわかります。また、itextの占める領域はファイル全体の1%にも満たず、違和感がありますが、次にステップの3章で動作させると理由がわかります。

3. エントリーポイントからヒープ領域の準備まで
エントリーポイントの逆アセンブル結果です。.itext領域の命令は、.text領域内のアドレスを、バッチのように呼ぶのみの処理をしていることがわかります。ですので.itext領域は小さくてよく、実際の動作コードは.text領域で動くことになります。call以外のコードはすべてダミーコードで、後半にWindowsAPI呼んでいるコードもダミーでした(そもそも、最後まで解析をすると、call dword_4255C8でexit processが呼ばれ、次のコードは実行されません)。こういったダミーコードをいれるのも解析妨害の1つです。

エントリーポイント最初のcall nullsub_1はダミー関数でした。そこで、次の関数である、sub_40639C(s_40639_prepare_heap)を見ていくことにします。
eaxレジスタに4バイトの固定値を格納して、さらに、固定のキー(4803BFC7h)でXORした値をhash_api_address_resolveと仮に名目した関数に入れると、入力値に応じた、WindowsAPIのアドレスが戻り値としてeaxに格納されます。
1番目の処理では、RtlCreateHeap関数の実アドレスが格納されていました。引数を積んだあとで4063C5で呼ばれている事がわかります。戻り値はハンドルで、esiにも格納されます。
2番目の処理では、RtlAllocateHeap関数の実アドレスでした。
この手法はAPI-hashingと呼ばれemotet等、多くのマルウェアで使われます。解析者はマルウェアが呼び出すWindowsAPIを手がかりに解析を行うので、API名をハッシュ化して検体内で持つこの手法は有効な妨害手段です。

CreateHeapで得たハンドルと、AllocateHeapの実アドレスを得たあとは、ヒープ領域にデータを加工して詰め込んでいく処理を延々と繰り返します。.text領域の別の場所にあるデータ(図ではdword_405EE8)のポインタを s_405DB0_create_heap_API_jmp_tableと仮に名前をつけた関数に呼ぶ処理を繰り返します。この意図が、現時点ではわかりにくいので、次の4章の解析を進め、5章でこのヒープ領域に詰め込む意図を解析することにします。 (★1)

4. 不審なジャンプコードの発見
解析をすすめると、.textセクション外にいきなりジャンプする不審なコードを見つけます。赤枠がジャンプ先のアドレス(0x026923F8)です。

奇妙なことにヒープ領域にコードが実行が移ります。固定値をeaxに入れた後、ror命令でビットシフト演算を行い、さらに、先程から見ている固定のキー(4803BFC7h)でXORし、またしても算出されたアドレスに直接ジャンプを行っているようです。

eaxの値を確かめます。ライブラリ関数の領域と思われます。

該当アドレスはコマンドライン引数を取得するWindowsAPI関数であるCommandLineoArgvWの実アドレスでした。この検体はコマンドライン引数を取り挙動変わるようです。今回は引数は無しで解析を実施しています。

同様にヒープアドレスへのcall命令です。スタックに文字列を積んでいます。

ヒープ上で、ror命令でビットシフト演算を行い、さらに、先程から見ている固定のキー(4803BFC7h)でXORし、算出されたアドレスに直接ジャンプします。ジャンプ先はCreateMutexWでした。先程、スタックで見た文字列がMutexとして作られることがわかります。多くのマルウェアで、多重感染を避ける等の理由からMutexが作られます。その値は、マルウェアファミリーや攻撃キャンペーンを示すIOCとして用いられることがあります。LockBit3.0では、Globalが固定値でそれ以下が感染端末固有のIDになっているようです。尚、Mutexは存在すると該当マルウェアファミリーに感染しないことから、俗にワクチンと呼ばれることがあります。

このヒープ領域を利用した方式を、ここでは、API_JMPテーブルと呼ぶことにします。
また、何回か最初から動作させてみたところ、同じAPI関数でもAPI_JMPテーブルが毎回異なることに気 づきました。同じCommandLineToArgvWですが、先程みたrorではなくrolを使い、シフトするビット 数も違います。キーは固定でした。この点も次の5章で解析していきます。

5. 再びヒープ領域の準備の処理を見る
3章の最後の部分(★1)で、.textにあるデータを何らかの演算をして、ヒープ領域にデータを正しく詰め込んでいく謎の挙動を見ました。解析をすすめると、これは4章で説明したAPI_JMPテーブルを構成していることがわかりました。
どのようにしてAPI_JMPテーブルを構成するか解析していきます。4バイト毎(dwordといいます)にメモリやレジスタから転送して処理を行うのですが、lodsdとstosd命令をおさらいしておきます。
- lodsd : [esi]の値をeaxに代入しesiを4増やす
- stosd : eaxの値を[edi]に代入しediを4増やす
create_heap_API_jmp_table関数は引数を4個とります。ヒープ記録アドレス(unk_42540C)、転送元アドレス(dword_405EE8)、heapハンドル、RelAllocateHeap関数の実アドレスです。

引数、ヒープ記録アドレス(unk_42540C)、転送元アドレス(dword_405EE8)をそれぞれ、edi、esiレジスタに格納します。そして転送元アドレスが終端コード(0xCCCCCCCC)ではないことを確認します。

eaxには転送元アドレスの4byteが入っている状態から、固定のキー(4803BFC7h)でxorを行うと、ターゲットとするAPI関数の実アドレスが戻りebxに格納します。次にサイズ10hのヒープ領域をRtlAllocateHeap関数を使って作ります、eaxにヒープ領域のアドレスが戻ります。stosdを用い、ヒープ記録アドレスに、ヒープアドレスを書き込みます(★3)。
mov [eax],0B8hで、ヒープ領域の先頭をB8h(x86バイトコードでmov ebx)に変更します。これは次のステップで説明する、ヒープ領域のメモリ構成を示す図の先頭のB8hに対応します。(★2)
最後に、0から4の値をgen_random関数で生成します。eaxに格納されます。

eaxが0,1,2,3,4の場合でそれぞれ分岐し、別の処理がなされます。

ここではeax=3の処理を見ていきます。まず、引数に1,9を取りgen_random関数を呼び出し、1-9のランダムな値を得て、ecx(cl)に格納します。
次に、固定のキー(4803BFC7h)をeaxに入れ、API_JMPテーブルの対象関数のアドレスが格納されているebxを固定キーでxorし、さらに、ランダム値clの値で、ebxをシフト演算します。
最後に、API_JMPテーブルのバイトコードを準備していきます。理解のために、対象とするAPI関数をCreateMutexW、ランダム値を5とした場合のヒープメモリの構成と対応するアセンブラを示しましたので、コードと合わせて参照ください。ヒープ領域の先頭はB8h(★2で準備)で、アセンブラだとmov ebxで、第2オペランドとして、上記で演算したebxの即値を配置します(図のオレンジ部分)。次に、ror命令に対応するバイトコードであるC8C1hを配置し(図の⻩色部分)、オペランドとしてランダム値 clを配置(図の緑部分)します。次に、xor命令に対応するバイトコードである35hを配置(図の紫部分)し、オペランドとして固定キー4803BFC7hを配置(図の⻘部分)します。さらに、jmp eax命令に対応するバイトコードであるE0FFhを配置(図の赤部分)します。


これらの操作によって、対象のWindowsAPIの1個のAPIのAPI_JMPテーブルが完成しました。ループにより対象とするすべてのAPIのAPI_JMPテーブルが順次作成されます。毎回ランダムにコードは生成されますが、ヒープ記録アドレス(★3)で管理するアドレスに到達させることで目的のAPIアドレスの呼び出しが可能となっています。
eax=3の場合の処理を見ましたが、他のパターンにも言及しておきます。eax=3では、rorとxorを使いましたが、rorの代わりにrolを使うパターン(eax=4)、ror及びrolのみ(eax=0,1)、xorのみ(eax=2)でした。
その他特徴的な点
擬似乱数生成にWindowsAPIではなくx86ネイティブ命令を使用
x86ネイティブ命令であるrdrandとrdseedを使い疑似乱数を生成していました。
同じrd系のrdtscは時間を計るのにマルウェアがよく使うことが知られますが、rdrand/rdseedは珍し く、作成者のアトリビューションに寄与するかもしれません。

MMX(MultiMedia eXtention) x86 のマルチメディア・レジスタの利用
MMXで拡張されたレジスタ(mm0-7)を使用していました。以下のケースでは、関数の戻り値の退避につかわれています。
MMXは古のIntel Pentium時代に追加された音声や動画の処理を高速化を目的とした命令セットで、なぜ、LockBit3.0がこのレジスタを使うのかは興味深いところではありますが、理由はわかりません。作成者固有のコンパイル環境に依存するのかもしれません。

マルチスレッドを多用
以下は検体実行時のイメージになりますが、10以上のスレッドがあることがわかります。暗号化の動作以外にも、様々な動作がマルチスレッド化されていました。LockBit3.0が高速化を売りにしている根拠となっているようです。

まとめ
LockBit3.0Builderを用いると容易にEncryptorとDecryptorをビルドでき、また、期待通りの動作も確認しており、攻撃者に悪用されると脅威となります。
LockBit3.0Encryptorは、呼び出すWindowsAPI名を隠すために、ヒープ領域に、ユーザコードとAPIを紐付けるためのジャンプコードを用意します。ジャンプコードは、シフト演算等で難読化がなされ、5パターンのバリエーションでランダムに生成されます。また起動ごとに各APIに対するジャンプコードは異なる作りになっています。
LockBit3.0Encryptorのその他の特徴として、擬似乱数生成にx86ネイティブ命令を使用したり、 MMXx86のマルチメディアレジスタを用いており、作成者の開発環境の特性が現れているのかもしれま せん。また暗号化の動作以外にもマルチスレッドを多用しています。
解析に用いたEncryptorのsha256
5defe0e424e73fd64c04ebc35bd8265ff5fcda6d7bcc97bf26dfd8d3136d69e
社会情報研究所NTT-CERT 田中 恭之、今野 俊一