5
1

More than 3 years have passed since last update.

マルウェアはセキュリティシステムやセキュリティエンジニアによる解析の検知や回避のためにRDTSC命令を使うことがあります.RDTSCはx86アーキテクチャが提供している,time stamp counter (TSC) を返す命令です.TSCはx86のCPUが保持している高精度の時間のカウンタです.TSCは64ビットの整数で,RDTSC命令を呼び出すとTSCの上位32ビットがEDXレジスタ,下位32ビットがEAXレジスタにセットされます.

TSC

すごく大まかな理解をするなら,TSCはそのCPUにおける経過CPUサイクル数を保持しているカウンタであり,1サイクル経過すると1増えます.でも,最近のCPUにはconstant TSCという機能が入っているため,TSCは正確には経過CPUサイクル数ではなく経過時刻のカウンタです.CPUは計算負荷などに応じてサイクルの周波数を変化させますが,たとえ周波数が変化してもTSCの増える速度は一定です.Intelのマニュアルの「TIME-STAMP COUNTER」という章にも以下の記述があります.

Constant TSC behavior ensures that the duration of each clock tick is uniform and supports the use of the TSC as a wall clock timer even if the processor core changes frequency.

なお,混同しやすい機能にinvariant TSCがありますが,こちらはACPIなどの作用でCPUがスリープなどの異なる状態に入ってもTSCの速度が保たれるというものです.

RDTSCは,CPUの非特権モードでも実行できます.なので,マルウェアを含むユーザレベルプログラムの一般ユーザ権限による実行でも,問題なく実行できます.ただし,特権モードでないと読めないように設定することも可能です.

マルウェアに限らず一般の低レイヤーの善良アプリケーションも,TSCを時刻情報,RDTSC命令を時刻取得命令として利用することがあります.一般のアプリケーションがRDTSC命令を時刻取得に利用することは推奨されないようですが,利点も多いため現実的にはよく利用されています.利点としては,時刻が高精度である以外にも,ライブラリを必要としないのでポータビリティが高く速度も速い,システムコールの実行を伴わないのでユーザレベルだけで事が済み,高速であるとともにフックや検知がされにくい,などがあります.

マルウェアがTSCやRDTSC命令を利用する目的

主要な目的の1つは高精度な現在時刻を得ることです.なにせ,大まかには1サイクルごとに1増えるカウンタですから,非常に細かい粒度で現在時刻を得ることができます.余談ですが,最近話題になったMeltdownやSpectreなどの投機実行を悪用したマイクロアーキテクチャ攻撃は,TSCのような高精度な時刻のカウンタがあったからこそ初めて可能になりました.

では,マルウェアは何のために高精度な現在時刻を得る必要があるかというと,自分が解析下にあるかどうかを時刻情報から推定するためです.たとえば自分がデバッガや仮想マシンの上で動いているのか,そうではないのかを推定するためです.通常,ハイパバイザが仮想化したりデバッガがフックしたりする命令や処理では,それらのシステムがいない場合に比べて,実行にかかる時間やCPUサイクル数は増えます.なので,そういう命令や処理を実行している間に経過した時間やCPUサイクル数を測れば,そういうシステムの存在を推定できます.

たとえばCPUID命令は実機上と仮想マシン上とで,消費する時間やCPUサイクル数が著しく変化することが知られています.CPUID命令を2つのRDTSC命令でサンドイッチして経過時間を計測するコードは,マルウェアなどが利用する典型的なハイパバイザ検知コードです.

以下は Paranoid fish (pafish) という著名なサンドボックス検出ツールのコードの抜粋です.RDTSC命令とCPUID命令を組み合わせています.

cpu.c
static inline unsigned long long rdtsc_diff_vmexit() {
    unsigned long long ret, ret2;
    unsigned eax, edx;
    __asm__ volatile("rdtsc" : "=a" (eax), "=d" (edx));
    ret  = ((unsigned long long)eax) | (((unsigned long long)edx) << 32);
    /* vm exit forced here. it uses: eax = 0; cpuid; */
    __asm__ volatile("cpuid" : /* no output */ : "a"(0x00));
    /**/
    __asm__ volatile("rdtsc" : "=a" (eax), "=d" (edx));
    ret2  = ((unsigned long long)eax) | (((unsigned long long)edx) << 32);
    return ret2 - ret;
}

対応するバイナリコードの逆アセンブル結果は以下の通り.無駄な命令がたくさんあり読みにくいですが,大まかにはCPUID命令を実行中のTSCの増分を計算しています.

pafish.exe
55                      push   %ebp
89 e5                   mov    %esp,%ebp
56                      push   %esi
53                      push   %ebx
83 ec 20                sub    $0x20,%esp
0f 31                   rdtsc
89 45 f4                mov    %eax,-0xc(%ebp)
89 55 f0                mov    %edx,-0x10(%ebp)
8b 4d f4                mov    -0xc(%ebp),%ecx
bb 00 00 00 00          mov    $0x0,%ebx
8b 45 f0                mov    -0x10(%ebp),%eax
ba 00 00 00 00          mov    $0x0,%edx
89 c2                   mov    %eax,%edx
b8 00 00 00 00          mov    $0x0,%eax
89 ce                   mov    %ecx,%esi
09 c6                   or     %eax,%esi
89 75 e8                mov    %esi,-0x18(%ebp)
09 d3                   or     %edx,%ebx
89 d8                   mov    %ebx,%eax
89 45 ec                mov    %eax,-0x14(%ebp)
b8 00 00 00 00          mov    $0x0,%eax
0f a2                   cpuid
0f 31                   rdtsc
89 45 f4                mov    %eax,-0xc(%ebp)
89 55 f0                mov    %edx,-0x10(%ebp)
8b 4d f4                mov    -0xc(%ebp),%ecx
bb 00 00 00 00          mov    $0x0,%ebx
8b 45 f0                mov    -0x10(%ebp),%eax
ba 00 00 00 00          mov    $0x0,%edx
89 c2                   mov    %eax,%edx
b8 00 00 00 00          mov    $0x0,%eax
89 ce                   mov    %ecx,%esi
09 c6                   or     %eax,%esi
89 75 e0                mov    %esi,-0x20(%ebp)
09 d3                   or     %edx,%ebx
89 d8                   mov    %ebx,%eax
89 45 e4                mov    %eax,-0x1c(%ebp)
8b 45 e0                mov    -0x20(%ebp),%eax
8b 55 e4                mov    -0x1c(%ebp),%edx
2b 45 e8                sub    -0x18(%ebp),%eax
1b 55 ec                sbb    -0x14(%ebp),%edx
83 c4 20                add    $0x20,%esp
5b                      pop    %ebx
5e                      pop    %esi
5d                      pop    %ebp
c3                      ret

私が解析したマルウェアの検体にも,CPUIDを含む数命令をRDTSC命令でサンドイッチしているコードが見られました(難読化のために無駄な命令が挿入されていましたが削除してあります).

malware.exe
0f 31           rdtsc
52              push   %edx
50              push   %eax
0f a2           cpuid  
0f 31           rdtsc
5f              pop    %edi
5e              pop    %esi
29 f8           sub    %edi,%eax
29 f2           sub    %esi,%edx
89 45 fc        mov    %eax,-0x4(%ebp)
89 55 f8        mov    %edx,-0x8(%ebp)
83 7d fc 06     cmpl   $0x6,-0x4(%ebp)
7f 01           jg     ...

自身が解析下にあるかどうかを知るためにRDTSC命令でサンドイッチするものは,CPUID命令である必要はありません.解析されている場合とされていない場合とで消費時間が大きく変化する処理であれば,何でもいいです.例えば以下の論文には,最近の現実のマルウェア検体群がRDTSC命令をどのように利用しているかを調査した結果が述べられています.たとえば,サンドイッチの「具」としてCPUID命令以外に,Sleep関数の呼び出しや10万回カウンタをデクリメントする意味なしループの実行などがあることなどが示されています.

一定の処理にかかる時間をRDTSC命令のサンドイッチで計測して解析を検知するテクニックは古くから知られています.例えば以下の2007年の論文や発表ですでに言及されています.

  • Thomas Raffetseder, Christopher Kruegel, Engin Kirda. Detecting System Emulators. In Proceedings of the 10th International Conference on Information Security (ISC 2007), pp 1-18, volume 4779 of Lecture Notes in Computer Science, Oct 2007.
  • Joanna Rutkowska, Alexander Tereshkin. IsGameOver(), anyone? (PDF). Black Hat USA 2007, Jul-Aug 2007.

仮想環境でのCPUID命令の実行の遅さを利用して仮想化を検知する手法は,例えば以下の2007年の論文ですでに言及されています.

RDTSC命令はout-of-order実行されるので,命令の実行順が入れ替わってTSCの値が意図に反する可能性があることには注意が必要です.Out-of-order実行を防ぐために,RDTSCP命令を使ったり,こちらの目的でもやはりCPUID命令を使ったりするテクニックがよく知られています.

5
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1