反沙箱(sanbox)多种技术-上篇

一、通过硬件规格实现反虚拟化

一般来说,虚拟化环境无法完全访问主机的硬件。恶意软件可以利用对硬件缺乏完全访问权限来检测它是否在虚拟环境或沙箱中执行。请记住,无法保证完全准确,因为机器可能只是以较低的硬件规格运行。将检查的硬件规格如下:
CPU — 检查处理器数量是否少于 2 个。
RAM — 检查是否少于 2 GB。
先前安装的 USB 数量 - 检查 USB 数量是否少于 2 个。

CPU 检查

可以使用 GetSystemInfo WinAPI 进行 CPU 检查。此函数返回一个 SYSTEM_INFO 结构,其中包含有关系统的信息,包括处理器数量。

 SYSTEM_INFO   SysInfo   = { 0 };
 GetSystemInfo(&SysInfo);
 if (SysInfo.dwNumberOfProcessors < 2){
    possibly a virtualized environment
  }

RAM 检查

请注意,这 2 * 1073741824 是 2 GB 的字节大小。

BOOL CheckRamNum() {
    MEMORYSTATUSEX MemStatus;
    MemStatus.dwLength = sizeof(MEMORYSTATUSEX);

    if (!GlobalMemoryStatusEx(&MemStatus)) {
        printf("\n\t[!] GlobalMemoryStatusEx Failed With Error : %d \n", GetLastError());
    }

    if ((DWORD)MemStatus.ullTotalPhys <= (DWORD)(2 * 1073741824)) {
        return TRUE;
    }
}

检查之前安装的 USB

  HKEY    hKey            = NULL;
  DWORD   dwUsbNumber     = NULL;
  DWORD   dwRegErr        = NULL;


  if ((dwRegErr = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\ControlSet001\\Enum\\USBSTOR", NULL, KEY_READ, &hKey)) != ERROR_SUCCESS) {
    printf("\n\t[!] RegOpenKeyExA Failed With Error : %d | 0x%0.8X \n", dwRegErr, dwRegErr);
  }

  if ((dwRegErr = RegQueryInfoKeyA(hKey, NULL, NULL, NULL, &dwUsbNumber, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) != ERROR_SUCCESS) {
    printf("\n\t[!] RegQueryInfoKeyA Failed With Error : %d | 0x%0.8X \n", dwRegErr, dwRegErr);
  }

  // Less than 2 USBs previously mounted
  if (dwUsbNumber < 2) {
    // possibly a virtualized environment
  }

完整代码

前面的代码片段被合并成一个函数 如果检测到虚拟化环境,IsVenvByHardwareCheck此函数将返回 。TRUE

BOOL IsVenvByHardwareCheck() {

	SYSTEM_INFO		SysInfo = { 0 };
	MEMORYSTATUSEX	MemStatus;
	MemStatus.dwLength = sizeof(MEMORYSTATUSEX);
	HKEY			hKey = NULL;
	DWORD			dwUsbNumber = NULL;
	DWORD			dwRegErr = NULL;

	// CPU CHECK
	GetSystemInfo(&SysInfo);

	// Less than 2 processors
	if (SysInfo.dwNumberOfProcessors < 2) {
		return TRUE;
	}

	// RAM CHECK
	if (!GlobalMemoryStatusEx(&MemStatus)) {
		printf("\n\t[!] GlobalMemoryStatusEx Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// Less than 2 gb of ram
	if ((DWORD)MemStatus.ullTotalPhys < (DWORD)(2 * 1073741824)) {
		return TRUE;
	}


	// NUMBER OF USBs PREVIOUSLY MOUNTED
	if ((dwRegErr = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\ControlSet001\\Enum\\USBSTOR", NULL, KEY_READ, &hKey)) != ERROR_SUCCESS) {
		printf("\n\t[!] RegOpenKeyExA Failed With Error : %d | 0x%0.8X \n", dwRegErr, dwRegErr);
		return FALSE;
	}

	if ((dwRegErr = RegQueryInfoKeyA(hKey, NULL, NULL, NULL, &dwUsbNumber, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) != ERROR_SUCCESS) {
		printf("\n\t[!] RegQueryInfoKeyA Failed With Error : %d | 0x%0.8X \n", dwRegErr, dwRegErr);
		return FALSE;
	}

	// Less than 2 usbs previously mounted
	if (dwUsbNumber < 2) {
		return TRUE;
	}

	RegCloseKey(hKey);

	return FALSE;
}

二、 通过机器解析实现反虚拟化

在沙盒环境中,机器的分辨率和显示属性通常会设置为一个标准化的、一致的值,与真实世界的机器的分辨率和显示属性可能会有所不同。因此,可以将分辨率较低的机器作为虚拟化环境的指标。
EnumDisplayMonitors 函数要求为其检测到的每个显示器执行一个回调函数,在此回调函数中, 必须调用GetMonitorInfoW WinAPI。此函数检索显示器的分辨率
获取的信息以MONITORINFO 结构的形式 返回 GetMonitorInfoW,如下所示。

typedef struct tagMONITORINFO {
  DWORD cbSize;			// The size of the structure
  RECT  rcMonitor;		// Display monitor rectangle, expressed in virtual-screen coordinates
  RECT  rcWork;			// Work area rectangle of the display monitor, expressed in virtual-screen coordinates
  DWORD dwFlags;		    // Represents attributes of the display monito
} MONITORINFO, *LPMONITORINFO;

该 rcMonitor 成员包含所需的信息。该成员也是一个 RECT类型的结构 ,通过其左上角和右下角的 X 和 Y 坐标定义一个矩形。检索结构的值后 RECT ,进行一些计算来确定显示器的实际坐标:

该 CheckMachineResolution 函数使用所描述的过程,通过执行回调来计算机器的分辨率 ResolutionCallback 。

#include <iostream>
#include <windows.h>

// The callback function called whenever 'EnumDisplayMonitors' detects an display
BOOL CALLBACK ResolutionCallback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lpRect, LPARAM ldata) {

	int X = 0,Y = 0;
	MONITORINFO  MI;
	MI.cbSize = sizeof(MONITORINFO);

	if (!GetMonitorInfoW(hMonitor, &MI)) {
		printf("\n\t[!] GetMonitorInfoW Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// Calculating the X coordinates of the desplay
	X = MI.rcMonitor.right - MI.rcMonitor.left;

	// Calculating the Y coordinates of the desplay
	Y = MI.rcMonitor.top - MI.rcMonitor.bottom;

	// If numbers are in negative value, reverse them
	if (X < 0)
		X = -X;
	if (Y < 0)
		Y = -Y;

	if ((X != 1920 && X != 2560 && X != 1440) || (Y != 1080 && Y != 1200 && Y != 1600 && Y != 900))
		*((BOOL*)ldata) = TRUE; // sandbox is detected

	return TRUE;
}


BOOL CheckMachineResolution() {

	BOOL	SANDBOX = FALSE;

	// SANDBOX will be set to TRUE by 'EnumDisplayMonitors' if a sandbox is detected
	EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)ResolutionCallback, (LPARAM)(&SANDBOX));

	return SANDBOX;
}

int main()
{

	BOOL isSandbox = CheckMachineResolution();

	if (isSandbox) {
		printf("Sandbox environment detected!\n");
	}
	else {
		printf("No sandbox detected.\n");
	}

	return 0;
}

三、通过文件名进行反虚拟化

沙盒通常会重命名文件作为分类的一种方法(例如将其重命名为其 MD5 哈希值)。此过程通常会导致文件名任意,包含字母和数字的混合。
下面显示的函数 ExeDigitsInNameCheck 用于计算当前文件名的位数。它使用 GetModuleFileNameA 获取文件名(包括路径),然后使用 PathFindFileNameA 将文件名与路径分开。
最后, isdigit 函数用于判断文件名中的字符是否为数字。如果文件名中的数字超过 3 位,则将 ExeDigitsInNameCheck 假定它在沙盒中并返回 TRUE

BOOL ExeDigitsInNameCheck() {

	CHAR	Path			[MAX_PATH * 3];
	CHAR	cName			[MAX_PATH];
	DWORD   dwNumberOfDigits	= NULL;

	// Getting the current filename (with the full path)
	if (!GetModuleFileNameA(NULL, Path, MAX_PATH * 3)) {
		printf("\n\t[!] GetModuleFileNameA Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// Prevent a buffer overflow - getting the filename from the full path
	if (lstrlenA(PathFindFileNameA(Path)) < MAX_PATH)
		lstrcpyA(cName, PathFindFileNameA(Path));

	// Counting number of digits
	for (int i = 0; i < lstrlenA(cName); i++){
		if (isdigit(cName[i]))
			dwNumberOfDigits++;
	}

	// Max digits allowed: 3
	if (dwNumberOfDigits > 3){
		return TRUE;
	}

	return FALSE;
}

四、通过正在运行的进程数量进行反虚拟化

检测虚拟化环境的另一种方法是检查系统上运行的进程数。沙盒通常不会安装太多应用程序,因此运行的进程较少。与之前的方法类似,这不是保证系统是沙盒的灵丹妙药。Windows 系统至少应该有 60-70 个进程在运行。

BOOL CheckMachineProcesses() {

	DWORD		adwProcesses	[1024];
	DWORD		dwReturnLen		= NULL,
	DWORD       dwNmbrOfPids	= NULL;

	if (!EnumProcesses(adwProcesses, sizeof(adwProcesses), &dwReturnLen)) {
		printf("\n\t[!] EnumProcesses Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	dwNmbrOfPids = dwReturnLen / sizeof(DWORD);

	// If less than 50 process, it's possibly a sandbox
	if (dwNmbrOfPids < 50)
		return TRUE;

	return FALSE;
}

五、通过用户交互实现反虚拟化

沙盒通常在无头环境中运行,这意味着没有显示器或外围设备,例如键盘和鼠标。无头环境通常也是自动化的,由脚本或其他工具触发。缺乏用户交互可能是沙盒环境的一个指标。例如,恶意软件可以检查环境是否在一段时间内没有收到任何鼠标点击或按键。
SetWindowsHookExW 和 CallNextHookEx WinAPI 用于跟踪鼠标点击。下面的函数中应用了相同的技术。 MouseClicksLogger如果它在 20 秒内没有收到超过 5 次鼠标点击,那么它将假定它处于沙盒环境中。

// ConsoleApplication70.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <windows.h>
// Monitor mouse clicks for 20 seconds
#define MONITOR_TIME   20000 // Global hook handle variable
HHOOK g_hMouseHook = NULL;
// Global mouse clicks counter
DWORD g_dwMouseClicks = NULL;

// The callback function that will be executed whenever the user clicked a mouse button
LRESULT CALLBACK HookEvent(int nCode, WPARAM wParam, LPARAM lParam) {

    // WM_RBUTTONDOWN :         "Right Mouse Click"
    // WM_LBUTTONDOWN :         "Left Mouse Click"
    // WM_MBUTTONDOWN :         "Middle Mouse Click"

    if (wParam == WM_LBUTTONDOWN || wParam == WM_RBUTTONDOWN || wParam == WM_MBUTTONDOWN) {
        printf("[+] Mouse Click Recorded \n");
        g_dwMouseClicks++;
    }

    return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam);
}


BOOL MouseClicksLogger() {

    MSG         Msg = { 0 };

    // Installing hook
    g_hMouseHook = SetWindowsHookExW(
        WH_MOUSE_LL,
        (HOOKPROC)HookEvent,
        NULL,
        NULL
    );
    if (!g_hMouseHook) {
        printf("[!] SetWindowsHookExW Failed With Error : %d \n", GetLastError());
    }

    // Process unhandled events
    while (GetMessageW(&Msg, NULL, NULL, NULL)) {
        DefWindowProcW(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
    }

    return TRUE;
}



int main() {

    HANDLE  hThread = NULL;
    DWORD   dwThreadId = NULL;

    // running the hooking function in a seperate thread for 'MONITOR_TIME' ms
    hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MouseClicksLogger, NULL, NULL, &dwThreadId);
    if (hThread) {
        printf("\t\t<<>> Thread %d Is Created To Monitor Mouse Clicks For %d Seconds <<>>\n\n", dwThreadId, (MONITOR_TIME / 1000));
        WaitForSingleObject(hThread, MONITOR_TIME);
    }

    // unhooking
    if (g_hMouseHook && !UnhookWindowsHookEx(g_hMouseHook)) {
        printf("[!] UnhookWindowsHookEx Failed With Error : %d \n", GetLastError());
    }

    // the test
    printf("[i] Monitored User's Mouse Clicks : %d ... ", g_dwMouseClicks);
    // if less than 5 clicks - its a sandbox
    if (g_dwMouseClicks > 5)
        printf("[+] Passed The Test \n");
    else
        printf("[-] Posssibly A Virtual Environment \n");


    printf("[#] Press <Enter> To Quit ... ");
    getchar();

    return 0;
}

posted @   aoaoaoao  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示