概要
指定したスレッドに関連付けられたトップレベルウィンドウを列挙するWin32 APIの関数です。
C++
BOOL EnumThreadWindows(
[in] DWORD dwThreadId,
[in] WNDENUMPROC lpfn,
[in] LPARAM lParam
);
第一引数が指定するスレッドID、第二引数がコールバック関数へのポインタ、第三引数がコールバック関数へ渡す値です。第一引数を0とすることで、全スレッドのトップレベルウィンドウを列挙することができるため、EnumWindowsと同様に使用することができます。
EnumThreadWindowsは、マルウェア解析環境の判別に使用されることがあります。代表的な判別方法は、トップレベルウィンドウの数をカウントし、攻撃者が指定した閾値より少ない場合をマルウェア解析環境と判断するものです。マルウェア解析環境では、マルウェア本体とマルウェア解析ツールのみを実行することがあります。そのため、一般的な端末と比較した場合、トップレベルウィンドウの数が少なくなる傾向にあります。そこで、EnumThreadWindowsを使用することでトップレベルウィンドウの数を取得し、通常よりも数が少ない環境がマルウェア解析環境と判断されます。
そのほかにもEnumThreadWindowsに加え、GetWindowTextやGetClassNameを使用することで、ウィンドウ名やウィンドウクラス名を取得し、攻撃者が作成したブロックリストの内容と一致する場合をマルウェア解析環境と判断する方法もあります。攻撃者が作成するブロックリストには、著名なマルウェア解析ツール(Autorunsなど)やデバッガー(OllyDbgなど)が指定されることが多いです。
実装の例
C++/x86 Assemblyのコード例を以下に示します。本コードでは、トップレベルウィンドウの数が10以下の場合、動作を終了します。
C++
BOOL CALLBACK EnumThreadWndProc(HWND hWnd, LPARAM lParam){
int* lpCount = (int*)lParam;
*lpCount += 1;
return TRUE;
}
BOOL isAnalyzedEnv(){
BOOL bRet = FALSE;
int nCount = 0;
EnumThreadWindows(0, EnumThreadWndProc, (LPARAM)&nCount);
if(nCount <= 10){
bRet = TRUE;
}
return bRet;
}
Assembly(x86)
mov [ebp+lParam], 0
lea eax, [ebp+lParam]
push eax ; lParam
push 8 ; lpfn
push 0 ; dwThreadId
call EnumThreadWindows
cmp [ebp+lParam], 10
jle short loc_analysisenvironment_found
回避手法
EnumThreadWindowsによるマルウェア解析環境検知を回避し、プログラムを解析する手法を以下に示します。一時的に検出を回避するときには1、2の手法を、恒久的に検出を回避するときには3の手法を使います。
- デバッグ中にレジスタの値を変更する
EnumThreadWindowsを呼び出した後に、lParamの値を変更します。変更する値は、攻撃者が指定したトップレベルウィンドウ数の閾値より大きくする必要があります。 - 意図的に複数のプロセスを実行する
マルウェア解析環境であると判断されないように、意図的に複数のプロセスを実行し、トップレベルウィンドウの数を増加させます。 - プログラムを書き換える
EnumThreadWindowsを呼び出した後の分岐命令をJMP命令またはNOP命令に書き換え、変更を保存します。
検出手法
EnumThreadWindowsが使用されたファイルを検出するためのYARAルールは下記のとおりです。
YARAルール
rule Win32API_EnumThreadWindows
{
strings:
$str = "EnumThreadWindows"
condition:
$str
}