Onkod:ダウンローダーと「ダウンローディー」

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

ダウンローダーは通常、マルウェア感染の「メインコース」をダウンロードするためのシンプルかつサイズの小さいファイルです。ダウンロードされたファイル(「ダウンローディー」)は必ずと言っていいほどダウンローダーよりも多くの特徴と機能を備えています。今回の記事では、W32/Onkodという比較的新しいダウンローダーの亜種とそれによりダウンロードされるファイルを見ていきましょう。

ダウンローダー

OncodはGetWindowThreadProcessId API、GetWindowRect API、GetDlgItemTextA APIを組み合わせて、271のAPI呼び出しのループに突入するため、初期解析にはかなり時間がかかりました。このループのイテレーション回数は(0x9C40)40,000となっており、合計10,840,000のAPI呼び出しが作成されました。これでは、アンチウイルスエンジンが命令をエミュレートしようとしても固まってしまいます。API呼び出しの作成後、このマルウェアは200ミリ秒スリープし、その後残りのコード部分に移ります。

シンプルなディクリプター

ダウンローダーのコードは、ファイルをダウンロードまたは保存するために必要なAPI名、ファイル入手元のURL以外は暗号化されていません。

「oha」という鍵を使ったシンプルなディクリプターが必要な文字列の復号に使用されます。

ディクリプターのルーチンにおいて、OnkodはCDQ命令、IDIV命令、ADD 命令を組み合わせ鍵文字列(「oha」)のハッシュ値を計算し、50バイトの鍵を生成します。

暗号化された文字列の各バイトはXORされ、50バイトの鍵から疑似乱数のバイトが取り出されます。

復号ルーチンが終了すると、OnkodはLoadLibraryA API を使ってwininet.dll、kernel32.dll、user32.dllというライブラリをロードします。これらのライブラリの名前は暗号化された文字列のリストに含まれています。

復号されたAPI名のアドレスに関しては、GetProcAddress APIを使って名前解決が行われます。

ダウンロードの実行...

必要なAPIの名前解決後、マルウェアはハードコードされたユーザーエージェントの文字列「Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0」でInternetOpenA APIを呼び出し、ダウンロードプロセスに備えます。そしてInternetOpenUrlA APIを使ってダウンロードリンクへの接続を確立します。

Onkodはsrand関数およびrand関数、さらにGetTickCount APIを用いて10桁の疑似乱数のファイル名を生成します。生成されたファイル名には「.exe」が付けられ、それ以前にGetTempPathA APIを使って生成されていたパス名%temp%に付け加えられます。そしてCreateFileA APIへの呼び出しを使い、「%temp%\3643476847.exe」というようなファイルを作成します。

InternetReadFile APIやWriteFile APIへの一連の呼び出しを用いてインターネットファイルがダウンロードされ、「%temp%\3643476847.exe」にコピーされます。

ファイルをダウンロードすると、OnkodはCreateProcessA APIを呼び出して「3643476847.exe」を実行します。

ダウンロードの最後には、MessageBoxA APIへの呼び出しを使って「Paint_ix.dll could not be found.」というメッセージボックスが表示されます。このメッセージはマルウェア内でハードコーディングされています。

最後に、ダウンローダーが終了し、ダウンロードされたファイルが新たなプロセスとして実行されます。

ダウンローディー

ダウンロードされたファイルが起動し、まず複数層から成る他の関数を呼び出す関数が呼び出されます。各関数には以下のものと類似した構造が含まれています。

MOV EAX,EBP PUSH EAX
XOR EBP,EBP ADC EBP,ESP
LEA SP,[ESP-60] PUSH ESI
PUSH EDI PUSH EDI PUSH EBX
CALL NextFunction

「MOV EAX, EBP」と「PUSH EAX」命令内のEAXレジスタは別のレジスタに変更することができます。例えば「MOV EBX, EBP」や「PUSH EBX」とすることも可能です。

次の3つの命令「XOR EBP,EBP」「ADC EBP,ESP」「LEA ESP,ESP-60」に関しては、60という値のみ変更可能となっており、それ以外は不変です。次の関数の呼び出し前にプッシュされるレジスタも数や配列を変更できます。

このマルウェアは必要な命令を含む関数に到達するまで、合計68の関数を呼び出します。重要な関数(後述)を実行した後、このマルウェアはこの68の関数間をトラバースし、最初の呼び出しに戻ります。これは重要な関数を覆い隠すため、解析を困難にしようとするトリックでしょう。他のマルウェアの場合にも、同様の方法で1つの重要な命令を実行するめに、何百というジャンプを実行することがあります。

プッシュ/ポップ データコピー

この重要な関数は、新たに割り当てられたメモリにマルウェアのコードをコピーするものです。

解析を困難にするための論理も賢いですが、それでもこのマルウェアはデータを移動させてコピーするというあまり標準的でない方法で自身の動作を隠そうとします。

「MOV EAX, 40」や「MOV EDI, 1000」という命令は40のような定数値をEAXに、1000というような定数値をEDIに置くために通常使用される手法です。OnkodはLEA(Load Effective Address)を使用して定数値をレジスタに置きます(図1参照)。

別のコンテキストでは、「SUB EAX, EAX」と「OR EAX, DWORD PTR DS:EBX」を使用してソースデータが取得されます。EBXから取得した値のORを取ると同じ値が出るため、この方法でもレジスタへデータを移動させることが可能です。

複数ブロックのデータやコードをコピーするには、通常のマルウェアであればREP(反復)命令やMOV命令を使ったシンプルなループを使用します。ループ内でデータやコードをスタックにプッシュ(PUSH EAX)し、それを指定されたメモリの場所に直接ポップ(POP DWORD PTR DS:EDI-4)することでも、同じタスクを完了させることができます(図1参照)。

シンプルな復号

ここでも重要な関数内で、マルウェアはVirtualAlloc APIへの呼び出しを使ってメモリブロックを割り当て、4バイトごとに復号を行い、それらを新たに割り当てられたメモリへコピーします。

EDXレジスタへ初期鍵を置き(PUSH 5CDE3199, POP EDX)、EDXレジスタから3を減算し(DEC EDX, DEC EDX, DEC EDX)、EAX内の値をEDXへ加えることで復号が実行されます。EAXレジスタにはソースデータの現在値が含まれており、EDXはデクリメントされた復号鍵を持っています。

次の復号鍵は現在のDWORD値から取得します(MOV EDX, DWORD PTR DS:EBX)。ここではリトルエンディアンの順序を逆にするため、4バイトがスワップされます(BSWAP EDX)。EBX は現在のDWORDのソースデータを指しています。

簡単に言えば、各DWORDの復号鍵はその前のDWORDから取得されるということです。

新たに割り当てられたメモリに(0x724)1,828バイトを復号してコピーした後、このマルウェアは68すべての関数呼び出しを通過して最初の呼び出しへ戻ります。

最初の呼び出しに到達すると、マルウェアは新たに割り当てられたメモリへとジャンプします。

割り当てられたコード

割り当てられたメモリで、OnkodはNEG命令、NOT命令、DEC命令、INC命令を使って「kernel32.dll」文字列の一部を復号し、LoadLibraryA APIを呼び出してkernel32.dllのイメージベースを取得します。

次いで、kernel32.dllのPEヘッダをパースしてエクスポートされた名前の数を取得し、最後にエクスポートされたAPI名の最後のRVA(相対仮想アドレス)の位置を特定します。

OnkodはROR(右回転)命令、ROL(左回転)命令、ADD命令を組み合わせてkernel32.dllのエクスポートされたAPI名のハッシュ値を計算します。最後にエクスポートされた名前を筆頭に各API名を算出し、マッチする値が見つかるまで特定のハッシュ値と照合していきます(図2参照)。

マッチしたAPI名のインデックスに基づき、APIのアドレスがAddressOfFunctionsのリストから名前解決されます。

メインアクト

APIアドレスの名前解決後、Onkodは(0x714)1,812バイトのコードを2つ目の割り当てられたメモリへコピーし、そこへ制御権を移転させます。

ダウンロードされたファイルのメインアクトに備え、このマルウェアはVirtualAlloc APIを使って3つ目のブロックの仮想メモリを割り当てます。次いで、GetModuleFileNameA APIとCreateFileA APIを組み合わせて元のダウンロードされたファイルを開きます。

SetFilePointe APIを使ってこのファイルのペイロードにファイルポインタをセットした後、Onkodはこのファイルから(0x5c000)376,832バイトを読み込み、それをReadFile APIを使って割り当てられた仮想メモリへ保存します。(これらのバイトはすでにメモリ内にあるダウンロードされたファイルの一部ですが、Onkodは一度も使用されていない物理バイトを好むようです。)

そしてOnkodは以下のシンプルなアルゴリズムを使って376,832バイトを復号します。

LODS DWORD PTR DS:[ESI]
PUSH EAX
XOR EAX,76A39421
BSWAP EAX
XOR EAX,EDX
STOS DWORD PTR ES:[EDI]

このアルゴリズムは開始するとまず、ESIが指すDWORD値をEAXにロードします。ESIは新たに割り当てられたバイトを指します。次いで、EAX値をスタックに保存し、0x76A39421でそれをXORします。EAXのXORされたバイトはBSWAP命令を使ってビッグエンディアンに並べ替えられ、EDXで再びXORされます。EDXの値はアルゴリズムを実行する前に計算されています。

「POP EDX」命令はEDXの鍵値をESIが指している現在のDWORDに変更します。このシンプルなアルゴリズムには2つの鍵値が使われます。1つがEDXのもので、もう1つが定数値0x76A39421です。

最後に、復号されたDWORDがEDIの指すメモリアドレスに書き込まれます。

変身

復号された376,832バイトを展開すると別のマルウェアが登場します。これはFakeAVの亜種として検出されています。FakeAVがどのように実行されるかについてはここでは話をしませんが、これがどのように呼び出されるのかを見ていきましょう。

通常の場合、バイナリコードに別の不正な実行ファイルを含むマルウェアは新たなマルウェアをドロップし、それを実行します。時には不正なコードが別のプロセスへ注入され、遠隔操作で呼び出されることもあります。

Onkodの場合、FakeAVコードがドロップされることも、注入されることもありません。

FakeAVコードの復号の後は、制御権が移転される前に以下の準備が行われます。

OnkodはVirtualProtect APIへの呼び出しを使ってダウンロードされたファイルのプロセス(イメージベースで始まる)のメモリ保護をPAGE_ READWRITEに変更します。そしてダウンロードされたファイルの元のMZ/PEヘッダをFakeAVのMZ/PEヘッダで上書きします。その後、VirtualProtect APIを呼び出してメモリ保護をPAGE_READONLYへ戻します。

そしてダウンロードされたファイルの最初のセクションの仮想メモリの場所を探し、そのメモリ保護をPAGE_READWRITEへ変更します。それからFakeAVの最初のセクション全体をダウンロードされたファイルの最初のセクションへコピーします。そしてVirtualProtect APIへの呼び出しを用い、ダウンロードされたファイルの最初のセクションのメモリ保護をPAGE_EXECUTE_READWRITEへ変更します。これでダウンロードされたファイルの最初のセクションが実行可能、読み込み可能、書き込み可能となります。

FakeAVのこの亜種には、3つのセクションしかありません。各セクションは慎重に計算され、最初のセクションで実行されたものと同様の手順を使ってダウンロードされたファイルの仮想メモリへとコピーされます。

OnkodはFakeAVのイメージベースがダウンロードされたファイルのイメージベースと類似しているかどうかを確認します。同じでない場合は、新たにダウンロードされたファイルのイメージベースを再調整します。

この時点で、ダウンロードされたファイルのコードはFakeAVのコードで上書きされています。これでダウンロードされたファイルがFakeAVになりました。

APIの名前解決

calc.exeのような実行ファイルが実行されると、オペレーティングシステムはそれが必要とするすべてのDLLとともにそれを仮想空間へとロードします。インポートテーブルにあるAPIはWindowsのオペレーティングシステムにより自動で名前解決されます。

FakeAVはダウンロードされたファイルのプロセス空間へ手動でコピーされるため、APIはまだ機能できる状態ではありません。そこで、以下の手順でAPIの名前解決が行われます。

FakeAVのイメージベースを調節した後、インポートテーブルの場所を特定するためにPEヘッダをパースします。Onkodがインポートテーブルへとジャンプし、インポートテーブルのサイズをインポートテーブルのアドレスに加えることで最初のDLLを探します。

そしてGetModuleHandleA APIへの呼び出しを使って最初のDLLのモジュールハンドルを回収します。ライブラリがプロセス内でまだロードされていない場合は、LoadLibraryA APIへの呼び出しが使用されます。そしてそのライブラリと関連付けられたAPI名の場所を特定するためにインポートテーブルをパースします。

OnkodがGetProcAddress APIを呼び出してAPI名のアドレスを名前解決し、それらをAddressOfFunctionsテーブルへ格納します。

そしてインポートテーブルの残りのAPI名をパースし、GetProcAddress APIを呼び出して各ライブラリに対して各APIの名前解決を行い、それらをAddressOfFunctionsテーブルへ保存します。

FakeAVがダウンロードされたファイルのプロセス空間で正しく動作するためには、上記の手順を実行することが非常に重要です。

制御権の移転

APIのアドレスが名前解決されたので、次はFakeAVへ制御権を移転させます。

OnkodはDllBaseフィールドの値がダウンロードされたファイルのプロセス空間にあるFakeAVのImageBaseと同じであるかどうかを確認することで、現在のモジュールのLDR_DATA_TABLE_ENTRY構造へ到達するまでPEB(Process Environment Block)をパースします。

次いでメモリ内のFakeAVの元のコピーのMZ/PEをパースします。元のFakeAVのEntryPointの値を取得し、それをイメージベースへ加えます。そして現在のモジュールのLDR_DATA_TABLE_ENTRY構造にあるEntryPointフィールドをFakeAVのエントリポイントで上書きします。

これでFakeAVのエントリポイントへの間接的ジャンプが実行されます。

まとめ

このダウンローダーの主な目的は「ダウンローディー」をダウンロードすることです。それは銀行系のトロイの木馬の場合もあれば、ファイルインフェクター、不正なワーム、FakeAV、その中間のものなど色々考えられます。Onkodのダウンローディーは一種のラッパーであるため、先に述べたように異なるペイロードを持つことが可能です。

ダウンロードされたファイルをFakeAVの亜種へ変身させるのはクールなトリックですね。FakeAVのコピーのドロップやプロセスへの注入を回避するため、Onkodは単純にダウンロードされたファイルをFakeAVのコードで上書きします。

マルウェアとの闘いとその検出に備え、マルウェアが用いるさまざまなトリックやテクニックを調査、立証していくために私たちは今後も研究を続けていきます。それでは次回まで、皆さんもご用心を!

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