概要
パフォーマンスカウンターの現在の値を取得するWin32 APIの関数です。GetTickCount関数やGetLocalTime関数と比較して、より高解像度(1マイクロ秒未満)のタイムスタンプを取得することが可能です。
C++
BOOL QueryPerformanceCounter(
[out] LARGE_INTEGER *lpPerformanceCount
);
GetTickCount関数やGetLocalTime関数と同様に、マルウェアはデバッガー上で実行されていることを検知するためにQueryPerformanceCounter関数を用いてコードの実行に掛かる時間を計測します。あるコードの実行時間が通常時に比べて大きい場合、マルウェアはデバッグされていると判断して自身のプロセスを終了します。
実装の例
ある処理の実行時間が通常時に比べて大きい場合にプロセスを終了させるコードの実装例は以下の通りです。
C++
BOOL isDebugged(LONGLONG estimatedElapsedTime) {
LARGE_INTEGER li1, li2;
QueryPerformanceCounter(&li1);
//処理に時間の掛かる動作
QueryPerformanceCounter(&li2);
return (li2.QuadPart - li1.QuadPart) > estimatedElapsedTime;
}
if isDebugged(1000) {
exit(1);
}
Assembly(x64)
lea rax, [rbp+PerformanceCount1]
mov rcx, rax
call QueryPerformanceCounter
;処理に時間の掛かる動作
lea rax, [rbp+PerformanceCount2]
mov rcx, rax
call QueryPerformanceCounter
mov rdx, qword ptr [rbp+PerformanceCount2]
mov rax, qword ptr [rbp+PerformanceCount1]
sub rdx, rax
cmp [rbp+arg_0], rdx
jb short loc_debugger_found
loc_debugger_found:
mov rcx, 1
call exit
回避手法
QueryPerformanceCounterによるマルウェア解析環境検知を回避し、プログラムを解析する手法を以下に示します。一時的に検出を回避するときには1の手法を、恒久的に検出を回避するときには2の手法を使います。
- デバッグ中に分岐命令を変更する
QueryPerformanceCounterを呼び出した後の分岐命令をJMP命令またはNOP命令に書き換えます。 - プログラムを書き換える
QueryPerformanceCounterを呼び出した後の分岐命令をJMP命令またはNOP命令に書き換え、変更を保存します。
検出手法
QueryPerformanceCounterを検出するYARAルールを以下に示します。
YARAルール
rule Win32API_QueryPerformanceCounter
{
strings:
$str = "QueryPerformanceCounter"
condition:
$str
}