マルウェアはセキュリティシステムやセキュリティエンジニアによる解析の検知や回避のために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命令を組み合わせています.
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の増分を計算しています.
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命令でサンドイッチしているコードが見られました(難読化のために無駄な命令が挿入されていましたが削除してあります).
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万回カウンタをデクリメントする意味なしループの実行などがあることなどが示されています.
- Yoshihiro Oyama. How Does Malware Use RDTSC? A Study on Operations Executed by Malware with CPU Cycle Measurement. In Proceedings of the 16th International Conference on Detection of Intrusions and Malware, and Vulnerability Assessment (DIMVA 2019), pp 197-218, volume 11543 of Lecture Notes in Computer Science, Jun 2019.
- 大山恵弘.マルウェアによるRDTSC命令の利用方法についての分析.コンピュータセキュリティシンポジウム 2018 論文集,pp. 754-761,2018年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年の論文ですでに言及されています.
- Tal Garfinkel, Keith Adams, Andrew Warfield, Jason Franklin. Compatibility is Not Transparency: VMM Detection Myths and Realities. In Proceedings of the 11th Workshop on Hot Topics in Operating Systems (HotOS '07), May 2007.
RDTSC命令はout-of-order実行されるので,命令の実行順が入れ替わってTSCの値が意図に反する可能性があることには注意が必要です.Out-of-order実行を防ぐために,RDTSCP命令を使ったり,こちらの目的でもやはりCPUID命令を使ったりするテクニックがよく知られています.