マルウェアによるスリープ処理の悪用


マルウェアはセキュリティシステムやセキュリティエンジニアによる解析の検知や回避のためにスリープ処理を使うことがあります.マルウェアがスリープを使う目的としては例えば以下のものがあります.

  1. 悪事を働くタイミングが来るまで待つため
  2. スリープによって動的解析をタイムアウトさせるため
  3. スリープによってサンドボックスやデバッガなどによる解析行為の存在を検知するため
  4. 良性のソフトウェアも実行するような,イベント発生待ちやプロセススイッチなどの普通の処理のため

マルウェア解析の観点から興味深いのは1, 2, 3です.

1は広く知られていると思います.たとえば発病する時刻がマルウェアのプログラムの中に埋め込まれていることがあります.ネットワーク通信によってサーバから攻撃開始の司令を受け取るまで待つこともあります.

2は1と似ていますが,待つ対象,というかスリープ終了のタイミングを決めるとき考慮する対象,が若干違います.2ではたとえばマルウェアは実行の最初で1時間スリープしたりします.動的解析では,多くの場合,5分とか15分とかの比較的短い時間がタイムアウト時間として設定されています.よって,解析対象のマルウェアが最初に1時間スリープすると,そのマルウェアは悪事を働く前にタイムアウトで解析が終了してしまいます.

3では,解析によって,プログラムが指定するスリープの長さと実際に行われるスリープの長さが変わってくるという性質を利用するものです.スリープの前後で現在時刻を取得し,自分が実際にスリープの長さの時間眠ったかどうかを検査します.たとえば1時間眠ることになっているのに現在時刻が1秒しか変わっていなかったら,自分は解析されていると判断します.

最近のサンドボックスの中には解析を効率化する機構やアンチ-アンチ-解析機構が組み込まれており,長時間のスリープをそのまま実行せず,スリープの長さを変えたり,スリープそのものをスキップしたりします.また,デバッガ上でスリープをフックしながらマルウェアを実行している場合にも,実際にスリープで停止している時間が,元々意図されているスリープの長さよりも長くなったり短くなったりすることがあります.

たとえばCuckoo Sandboxでは以下の2条件が成り立つときに,マルウェアが試みるスリープの長さを強制的に非常に短い時間に変更します.

  • マルウェアの実行が始まってから5秒以内である
  • マルウェアがプロセスもスレッドも生成していない

1つ目の条件は,解析をタイムアウトさせるためのスリープはプログラムの冒頭で実行されることが多いという経験則に基づいて導入されていると推測しています.

2つ目の条件は,一見なんで導入されたのかわからない条件かもしれませんが,マルウェアが複数のプロセスやスレッドからなる場合には,うかつにスリープの長さを変えると解析行為を検知されうることが知られています(詳しくは文献[1,3]などに記述あり).

先日紹介した,マルウェアが解析の存在を検知するための典型的な処理を多数実行するツールである Pafish にも,マルウェアによるスリープに関するテストが入っています.

pafish/pafish/gensandbox.c
int gensandbox_sleep_patched() {
    DWORD time1;

    time1 = GetTickCount();
    Sleep(500);
    if ((GetTickCount() - time1) > 450 ) return FALSE;
    else return TRUE;
}

この関数では500ミリ秒のスリープを試みます.そして,そのスリープの前後で時刻を取得し,時刻の差分が450ミリ秒より大きいかどうかを検査します.もし大きければ解析されていない,そうでなければ解析されていると判断します.

以下のスクリーンショットの実行では "Checking if Sleep() is patched using GetTickCount()" の結果が OK になっています.これは「解析されていない」という判定です.もしスリープの長さを改変すると,ここが traced! に変わります.

同様のツールである Al-Khaser にも,スリープのスキップをテストする以下のコードが含まれています.

al-khaser/al-khaser/AntiVM/Generic.cpp
/*
Sleep and check if time have been accelerated
*/
BOOL accelerated_sleep()
{
    DWORD dwStart = 0, dwEnd = 0, dwDiff = 0;
    DWORD dwMillisecondsToSleep = 60 * 1000;

    /* Retrieves the number of milliseconds that have elapsed since the system was started */
    dwStart = GetTickCount();

    /* Let's sleep 1 minute so Sandbox is interested to patch that */
    Sleep(dwMillisecondsToSleep);

    /* Do it again */
    dwEnd = GetTickCount();

    /* If the Sleep function was patched*/
    dwDiff = dwEnd - dwStart;
    if (dwDiff > dwMillisecondsToSleep - 1000) // substracted 1s just to be sure
        return FALSE;
    else
        return TRUE;
}

60秒のスリープを実行し,そのスリープによる実際の経過時間が59秒より大きければ,解析されていないと判断します.そうでなければ解析されていると判断します.

以下のスクリーンショットの実行では "Check if time has been accelerated" の結果が [ GOOD ] になっています.これは「解析されていない」という判定です.もしスリープの長さを改変すると,ここが [ BAD ] に変わります.

マルウェアが実行するスリープの悪用の問題を解決する方法については,非常に多くの研究があります.例えば文献[5]ではCuckoo Sandboxのスリープスキップの挙動や,ネットワークから時刻情報を取得してスリープスキップを検出するプログラムが議論されています.スリープの悪用の問題を完全に解決するのは極めて難しく,永遠にいたちごっこを続けるしかないというのが私の考えです.だからこそこれに関する研究には終わりがなく,論文も出続けているのだと思います.私も過去に,マルウェアが実行するスリープ処理を主に扱う以下の論文[1-4]を書きました.

システムセキュリティ研究室 研究成果
https://syssec.cs.tsukuba.ac.jp/wp/index.php/work/

  1. Yoshihiro Oyama. Skipping Sleeps in Dynamic Analysis of Multithreaded Malware. Proceedings of the 2018 IEEE Conference on Dependable and Secure Computing (IEEE DSC 2018), pp. 164-171, IEEE, Dec 2018.
  2. Yoshihiro Oyama. Investigation of the Diverse Sleep Behavior of Malware. Journal of Information Processing, Volume 26, pp. 461-476, June 2018. (Recommended Paper)
  3. 大山恵弘.動的マルウェア解析においてスリープ時間を短縮する方式.コンピュータセキュリティシンポジウム 2017 論文集,pp. 487-494, 2017年10月.
  4. 大山恵弘.マルウェアのスリープ挙動の多様性に関する予備調査.情報処理学会研究報告 コンピュータセキュリティ(CSEC),Vol. 2017-CSEC-76, 2017年2月.
  5. Alexander Chailytko and Stanislav Skuratovich. Defeating Sandbox Evasion: How to Increase Successful Emulation Rate in your Virtualized Environment. Proceedings of the 26th Virus Bulletin Conference, 2016.