HOOK

描述

该技术通过检测主机是否存在不同于正常主机的HOOK来判断当前环境是否为虚拟环境。

1.检查系统函数内是否设置了HOOK

恶意软件可以获取或控制系统中的其他进程的内存,并检查Windows api函数是否被挂钩,如果被挂钩则不运行,从而规避检测。
以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函数为例:
#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个参数:
; 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!");
}