ソルテッドアルゴリズム - パート1

本記事はVirus Bulletinに掲載されたものです。

Salityは何年も前から存在していますが、今でも最も普及しているマルウェアの1つです。

今回の記事では、実行可能ファイルに感染するだけでなく、トロイの木馬的な属性を持つSalityの亜種に焦点を当てていきます。こうした不正な機能の組み合わせは最近のマルウェアでは珍しくはありませんが、これらのマルウェアがなぜデジタル界からなくならないのかを理解するためには研究を続けることが重要です。

今回の記事は2部構成になっています。第1部ではこのマルウェアの検出を困難にしている多重の復号化、デコーディング、その他アルゴリズムを見ていきます。また、メインスレッドおよび、システム操作を実行するスレッドについても見ていきます。第2部(来月掲載予定)では、メインスレッドが生成する第一層スレッドと、さらにそれらが生成するスレッドの解説を行います。

第一段階

Salityのこの亜種には実行ファイルであるLauncher.exeが入っています。ファイルを感染させる前に、別のコンテキストで実行されることになるインフェクターのコードの準備をします。

「M^4」を探す

このマルウェアはPEB(プロセス環境ブロック)をパースして、1つ目のInLoadOrderModuleList構造体にあるモジュールのパス名を取得し、保存します。これは後々使用します。

そしてマルウェアコードのエントリポイントで始まる16進数バイトをパースし、「M」という文字を探します。「M」が見つかるたびに、その次の2バイトが「^4」になっているかどうかをチェックし、完全一致を探し続けます。

「M^4」はマルウェアがコード内の境界を特定するために、終端を示すマーカーとして使用しているものです。このマーカーのすぐ後には、暗号化された16進数バイトのサイズ(0x434)があり、これは後に処理されます。

仮想メモリとしてスタック

VirtualAllocあるいはVirtualAllocEx APIへの呼び出しは、マルウェアが仮想メモリ内に空間を作成するために一般的に使用される手法です。新たに作成された仮想メモリ空間は、マルウェアのメモリのスクラッチパッドの役割を果たします。これはマルウェアが暗号化/復号ルーチンを実行する際にスワップ領域として使用することができ、16進数バイトをファイルからメモリへ、あるいはメモリからファイルへ移動したり、メモリ操作が必要とされる際に用いられます。

ですが、Salityは少々異なる方法を用います。前述のAPIを使用して仮想メモリを作成する代わりに、スタック用に割り当てられた空きメモリ空間を使用するのです。(スタックとはデータのプッシュあるいはプルが行われるメモリの一種です。これはFIFO(First-In- First-Out、先入れ先出し)とも呼ばれ、メモリアクセスに使用されます。スタックで使用中のデータを破壊しないように、Salityはよく使用される領域から離れた場所にアドレスポインタを指定します。現在のベースポインタ(EBP)に0xFFFF81DFを追加し、これをデータ操作の開始位置として指定します。

開始位置が指定されると、マルウェアは0x434の16進数バイトをスタックメモリへコピーします。これらの16進数バイトは先ほど話をした境界のアドレスで始まるマルウェア本文に入っているものです。正確な開始位置は終端を示すマーカー(M^4)の後に来る境界のアドレス+0x14になります。

ツーパス方式の復号

マルウェアのメモリ空間からスタックメモリへ0x434バイトをコピーした後、Salityはコードを二度復号します。

最初のパスでは、マルウェアはシンプルなSUB(減算)命令を使ってバイト単位でコードを復号します。スタックからバイトを読み込み、0x1420(終端を示すマーカー「M^4」の5番目のバイトから取った値)を減算し、その結果得られたバイトをスタックに格納します。そして、スタック内にある0x434バイトに対して減算を実行します。

2回目のパスでも0x434バイトに対してこのプロセスを繰り返しますが、減算を用いる代わりに、このマルウェアは各バイトに対してシンプルなXORを用います。XORキーは、減算ルーチンで使用されたものと同じ鍵(0x1420)+7です。命令は「XOR byte, 0x1427」となります。

SUBおよびXOR命令はマルウェアコードを復号するたびにDWORDを使用しますが、結果として得られるバイト(すなわち、DWORD全体ではない)のみがマルウェアに関連があります。

ブロック内の新たなコード

復号ルーチンの後、マルウェアは本文内にあるBase64でエンコードされたコードのブロックの位置を保存します。そして、スタック内の新たに復号されたコードの開始部に実行権を移行させます。

新たに復号されたコードの冒頭で、SalityはPEBを再びパースし、カレントモジュールのパス名を取得します。パス名からドライブレターを除き、現在の実行可能ファイルの最初の文字が「m」で始まるか、あるいは2番目の文字が「y」になっているかを確認します。どちらにも当てはまらない場合、Base64でエンコードされたブロックの位置へそのままジャンプします。マルウェアはこのブロックがすでにデコードされたものと見なしています。

また、パス名の12番目の文字が「e」かどうかもチェックし、そうである場合にもブロックがデコードされたものであり、同時に自身の実行可能ファイルの1つであると見なします。

Base64エンコードとは少々異なる

上記の条件が満たされないと、Salityはカスタム式のBase64エンコーディングスキームに使用する文字の設定へと進みます。このマルウェアはBase64でエンコードされたテキストのデコーディングに使用されるものと同じ手法を使用しますが、インデックス文字は異なるものを使用します。この亜種に使用される64文字のシーケンスは「0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+/」となっています。これは標準的なBase64エンコードの文字とは少し異なります。

このマルウェアは0x1DD76のテキスト文字をデコードし、暗号化された別のコードの0x16618バイトと同じものを生成します(図1参照)。

エンコード+暗号化
デコード+復号


図1:オフセット0x00406044で始まるコード部分

デコードされたものを復号

Base64でデコードされたバイナリは次のアルゴリズムを使用して復号されます。

XOR EDX,EDX
MOV DL,BYTE PTR DS:[EAX+EBX]
MOV ECX,EAX
IMUL ECX,DWORD PTR SS:[EBP+10] XOR EDX,ECX
MOV BYTE PTR DS:[EAX+EBX],DL
INC EAX

デコードされたバイナリの開始位置から、DLレジスタに各バイトが置かれます。現在位置がEAXからECXに保存され、ECXはDWORD PTR SS:[EBP+10]にあるキー(0x2210)で乗算されます(IMUL)。キー(0x2210)は復号ルーチンでは一定のままです。

最後に、EDXレジスタ内のバイト(DL)が、乗算の結果であるECXでXORされます。そしてそれがBYTE PTR DS:[EAX+EBX]で現在のメモリ位置に保存されます。

復号後、制御権は復号されたバイナリコードへ移行されます(図1参照)。

セルフコード注入

デコーディングと復号プロセスの後、マルウェアはPEBをパースし、kernel32.dllのベースを取得します。kernel32.dllの位置が判明すると、エクスポートテーブルをパースし、テーブル内にあるAPI名のリストの各文字列をチェックしてGetProcAddress APIを探します。

そしてGetProcAddress APIを使用し、マルウェアが不正な活動を行うために必要なすべてのAPIを生成します。その後、バイナリのブロックを復号し、実行可能ファイルのイメージを作成します。このイメージには完全な状態のMZ/PEヘッダと、コードおよびデータの残りの部分が含まれています。

この実行可能ファイルのドロップおよび実行は容易に行えますが、Salityはその代わりにサスペンドモードでCreateProcessAを使用して新たなプロセスを生成します。この新たなプロセスは同じモジュール名を持つ自身のコピーとなっています。

復号されたイメージはその後、WriteProcessMemory APIを使ってこの新たなプロセスへ注入されます。このAPIへ一連の呼び出しを行うことで、イメージ全体をコピーし、そして元のSalityのモジュールとは異なる完全に新たなプロセスを作成するのです。

これは新しい技法ではありませんが、ドロップや、ドロップされたファイルの実行を監視するヒューリスティック検出に対してはかなり有効的です。

セットアップが完了すると、このマルウェアはサスペンドしていたプロセスを再開し、元のアプリケーションを終了します。元のプロセスにセットされていたブレークポイントは一切トリガーされることはなく、従ってその後の解析を回避します。

アンチデバッグ機能

自身の新バージョンを生成した後、Salityは不正活動を実行します。しかし、マルウェアがデバッガーのコンテキストにロードされている状態では、これらの活動をアナリストが観察するのは容易ではありません。

このマルウェアはデバッガー内でバイナリのほとんどを復号し、複数のスレッドを生成していきます。最初のスレッドが生成されると、Salityは存在しないメモリ位置へ意図的にアクセスしてデバッガーをクラッシュさせる例外を作成します。

通常の実行では、新たなスレッドは自身のコンテキストで実行されることになるため、例外は無視されます。ですがデバッガー内にある場合には、メインスレッドが例外を呼び出す前に最初のスレッドを実行する方法を見つけなければなりません。

メインスレッド

メインスレッドの主な目的はマルウェアコードを復号してそれを実行することです。各4バイト(DWORD)がそれぞれ403の命令を持つ32のイテレーションを使用して復号されます。つまり、1つのDWORDを復号するのに、およそ12,896の命令が必要になるということです。最初のDWORDの正確な位置を特定するためにコードをトレースするだけでも時間がかかります。

かなりの数のIMUL、SHL、そしてジャンプ命令が各WORDの復号を実行するために割り当てられます。多数のジャンプ命令があり、コード内のほぼどの部分にも移動できます。(0xFEE8)65,256バイトを復号した後、Salityは新たに復号されたコードに制御権を移行します。

最初のプロセスで行ったように、SalityはPEBをパースしてkernel32.dllを取得します。そしてkernel32.dllのエクスポートテーブル内のAPI名のリストをパースし、LoadLibraryExA APIとGetProcAddress APIを探します。

kernel32が正しいものであることを確実にするため、マルウェアはLoadLibraryExA API を使ってkernel32.dllをリロードし、GetProcAddress APIを使って残りの必要なAPIの名前解決を行います。

SalityはINVALID_HANDLE_VALUEをファイルハンドルとした「hh8geqpHJTkdns0」と「purity_control_90833」という2つのファイルマッピングオブジェクトを作成します。結果として得られたファイルマッピングオブジェクトはどの通常ファイルとも関連付けられていません。これらは基本的に新たに生成されるメモリセクションの名前として使用されます(図2参照)。


図2: ミューテックス名とセクション名

ファイルマッピングオブジェクト「purity_control_90833」にセットされたハンドルを使ってMapViewOfFile APIを呼び出し、VirtualAlloc APIを呼び出すのと同様の方法でメモリ空間を生成します。そして、すでに復号されている65,256バイトを新たな仮想メモリ空間へコピーします。

その後、自身のコンテキストで実行される新たなスレッドを作成します。新たに作成されたスレッドがメインスレッドに対し、適切に実行していることを示すシグナルを送信します。このシグナルを受け取らないと、メインスレッドは例外を生成します。

メインスレッドは第一層スレッドの生成も行います。

システムコンフィギュレーション スレッド

このスレッドはカレントプロセスのファイル名を格納し、「uxJLpe1m」という名前のミューテックスを作成します。そしてVirtualAlloc APIを使って仮想メモリ空間を作成し、実行可能イメージをコピーします。この実行可能イメージはメインスレッドから復号された65,256バイトから取ってきたものです。このイメージには通常のMZ/PEヘッダとUPX0、UPX1、UPX2というセクション名が入っており、UPXで圧縮された実行可能ファイルであることを示しています。そしてメモリ内のUPX実行可能ファイルへ制御権を移行する前に、必要なAPIの名前解決を行います。

UPX内で初めにすることは、マルウェアのメインコードの解凍です。解凍後、SalityはWSAStartup APIを呼び出し、 Windows Sockets関数の使用を初期化します。後々使用するためです。

そして[HKEY_CURRENT_USER\Software\Microsoft\ Windows\CurrentVersion\Explorer\Advanced]キー内のレジストリエントリ「Hidden」を2に変更することで、ファイルとフォルダを非表示に指定します(図3参照)。


図3: ファイルとフォルダの隠し属性を指定するオプション

オーバーライドと無効化

システムコンフィギュレーション スレッドでは、Salityは[HKEY_LOCAL_MACHINE\ SOFTWARE\Microsoft\Security Center]キーにある以下のデータをセットします。

AntiVirusOverride
AntiVirusDisableNotify
FirewallDisableNotify
FirewallOverride
UpdatesDisableNotify
UacDisableNotify

これらの値を1に指定するとアンチウイルス、ファイアウォール、UAC関連の通知が無効化されます。通常、これらの通知はユーザーが使用しているアンチウイルス ソフトウェアやファイアウォール、ユーザーアクセス制御設定の状態をユーザーに知らせます。例えば、アンチウイルス ソフトウェアのアップデートが必要な場合やファイアウォールが無効になっている場合、推奨されないセキュリティレベルのファイルアクセスが行われている場合にユーザーに警告を送ります。また、このマルウェアは[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ Security Center\Svc]キーにあるものと同じデータセットを作成し、セットします。

セキュリティセンターのレジストリキーをセットした後は、[HKEY_CURRENT_USER\Software\ Microsoft\Windows\CurrentVersion\Internet Settings\ GlobalUserOffline] を0に指定して、Internet Explorerがオフラインモードにならないようにします。

セキュリティ機能を無効化

ここでもシステムコンフィギュレーション スレッドにおいて、Salityは[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\ CurrentVersion\policies\system]キーのEnableLUA サブキーを0に指定し、UAC(ユーザーアカウント制御)を無効化します。

UACはオペレーティングシステムのセキュリティ機能であり、イベントやアクションがコンピュータに危害を加える可能性がある場合にユーザーに許可を促します。この機能を無効にすることで、気づかれることなく不正活動を実行できるようになります。

ファイアウォール設定の操作

最後に、Salityはカレントモジュール名を[HKEY_LOCAL_MACHINE\ SYSTEM\ControlSet001\Services\SharedAccess\ Parameters\FirewallPolicy\StandardProfile\ AuthorizedApplications\List]にあるファイアウォールの例外リストに追加します。これはファイアウォールによるブロックを迂回するためです。モジュール名は<モジュール名>:*:Enabled:ipsecという形式になっています。

このマルウェアは念には念を入れます。自身をファイアウォールの例外リストに追加するだけでなく、EnableFirewallサブキーを0に指定してファイアウォールの無効化も行うのです。さらに例外が間違いなく許可されるように、DoNotAllowExceptionsサブキーを無効化します。DisableNotificationsサブキーに値1を入れると、通知も無効化されます。これらのサブキーは[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\ Services\SharedAccess\Parameters\FirewallPolicy\ StandardProfile]キーで確認することが可能です。

さらなるスレッド

Salityはスレッドを次から次へと生成します。中には他のスレッドから提供される情報を待つだけのものもありますが、各スレッドが特定のタスク専用のものとなっています。

私たちはすでに、いくつかのスレッドが活動しているところを観測しました。本記事の第2部では、コード注入を行うもの、ファイル感染を行うものなどを見ていきます。

今回の記事およびその他の記事をダウンロードするには、Virus Bulletinにアクセスしてください。