This commit is contained in:
Print3M
2023-09-15 22:06:35 +02:00
commit a3929ff50c
8 changed files with 459 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# Anti-VM techniques
Basic implementation of several anti-vm techniques (Windows) for educational purpose. They rely on checking different parts of OS and hardware to find out if the script is running in a VM.
## Implemented techniques
[x] CPU hypervisor bit (CPUID)
[x] CPU id string (CPUID)
[x] CPU brand string (CPUID)
[x] BIOS manufacturer string
[X] BIOS version string
[x] Screen resolution
[x] Amount of physical memory
[x] Number of CPU cores
[x] Amount of disk space
## To be implemented
[ ] Global Descriptor Table location
[ ] Local Descriptor Table location
[ ] Interrupt Descriptor Table location
[ ] ACPI VM-based string checks
[ ] VM-based hostnames and usernames
[ ] VM-based MAC addresses
+158
View File
@@ -0,0 +1,158 @@
#include <string>
#include "detectors.hpp"
#include <windows.h>
#include <intrin.h>
#include <array>
#include <string>
#include "wmi.hpp"
#include <Wbemidl.h>
#include <iostream>
#include "utils.hpp"
#include <vector>
std::vector<std::string> detect::vm_names = { "virtual", "qemu", "vmware", "oracle", "innotek" };
std::vector<std::string> detect::legit_cpu_ids = { "AuthenticAMD", "GenuineIntel" };
bool detect::cpu_hypervisor_bit() {
/*
Check for hypervisor - enabled bit.
eax = 0x1, 31st-bit of ECX
*/
std::array<int, 4> cpuInfo = { 0, 0, 0, 0 };
__cpuid(cpuInfo.data(), 0x1);
return (cpuInfo[2] >> 31) & 0x1;
}
bool detect::cpu_id() {
/*
Check for fake-vm CPU ID string.
eax = 0x0, EBX + ECX + EDX (12 bytes)
*/
// TODO: I think there is something wrong with the regs order.
std::array<int, 4> cpuInfo = { 0, 0, 0, 0 };
std::string cpu = "";
__cpuid(cpuInfo.data(), 0x0);
for (int i = 1; i <= 3; i++) {
cpu += cpuInfo[i] & 0xff;
cpu += (cpuInfo[i] >> 8) & 0xff;
cpu += (cpuInfo[i] >> 16) & 0xff;
cpu += (cpuInfo[i] >> 24) & 0xff;
}
return !utils::str_includes(cpu, detect::legit_cpu_ids);
}
bool detect::cpu_brand() {
/*
Check for fake-vm CPU brand name.
eax = 0x80000002, 0x80000003, 0x80000004
(EAX + EBX + ECX + EDX) x 3 = 36 bytes
*/
std::array<int, 4> cpuInfo = { 0, 0, 0, 0 };
std::string cpu = "";
for (int id = 2; id <= 4; id++) {
__cpuid(cpuInfo.data(), 0x80000000 + id);
for (auto i : cpuInfo) {
cpu += std::tolower(i & 0xff);
cpu += std::tolower((i >> 8) & 0xff);
cpu += std::tolower((i >> 16) & 0xff);
cpu += std::tolower((i >> 24) & 0xff);
}
}
return utils::str_includes(cpu, detect::vm_names);
}
bool detect::bios_manufacturer() {
/*
Check for fake-vm BIOS manufacturer string.
*/
std::string manufacturer = "";
wmi::get_wmi("select * from Win32_BIOS", [&](IWbemClassObject* pclsObj, VARIANT* vtProp) {
HRESULT hr = pclsObj->Get(L"Manufacturer", 0, vtProp, 0, 0);
manufacturer = utils::lowercase(utils::bstr_to_str(vtProp->bstrVal));
});
std::cout << manufacturer << std::endl;
return utils::str_includes(manufacturer, detect::vm_names);
}
bool detect::bios_version() {
/*
Check for fake-vm BIOS version string.
*/
std::string version = "";
wmi::get_wmi("select * from Win32_BIOS", [&](IWbemClassObject* pclsObj, VARIANT* vtProp) {
HRESULT hr = pclsObj->Get(L"SMBIOSBIOSVersion", 0, vtProp, 0, 0);
version = utils::lowercase(utils::bstr_to_str(vtProp->bstrVal));
});
return utils::str_includes(version, detect::vm_names);
}
bool detect::screen_resolution() {
/*
Check for uncommon screen resolutions.
*/
auto w = GetSystemMetrics(SM_CXSCREEN);
auto h = GetSystemMetrics(SM_CYSCREEN);
// Standard and common resolutions
if (w == 1600 && h == 900) return false;
if (w == 1920 && h == 1080) return false;
if (w == 1920 && h == 1200) return false;
if (w == 2560 && h == 1440) return false;
if (w == 3840 && h == 2160) return false;
return true;
}
bool detect::memory_amount() {
/*
Check for suspicious amount of physical memory.
*/
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
if (GlobalMemoryStatusEx(&statex) == FALSE) {
return true;
}
// Minimum 4 GB
return statex.ullTotalPhys / 1024 / 1024 < 4096;
}
bool detect::cpu_cores() {
/*
Check for suspicious amount of CPU cores.
*/
SYSTEM_INFO info;
GetSystemInfo(&info);
return info.dwNumberOfProcessors < 4;
}
bool detect::disk_space() {
/*
Check for suspicious amount of total disk space.
*/
ULARGE_INTEGER bytes;
BOOL result = GetDiskFreeSpaceExA(
"\\\\?\\c:\\",
NULL,
&bytes,
NULL
);
if (result == FALSE) return true;
return bytes.QuadPart / 1024 / 1024 / 1024 < 100;
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <vector>
namespace detect {
extern std::vector<std::string> vm_names;
extern std::vector<std::string> legit_cpu_ids;
bool cpu_hypervisor_bit();
bool cpu_id();
bool cpu_brand();
bool bios_manufacturer();
bool bios_version();
bool screen_resolution();
bool memory_amount();
bool cpu_cores();
bool disk_space();
};
+25
View File
@@ -0,0 +1,25 @@
#include <stdio.h>
#include <windows.h>
#include <intrin.h>
#include <array>
#include <string>
#include <Wbemidl.h>
#include <iostream>
#include "detectors.hpp"
/*
TODO:
- memory && CPU resources
*/
int main(int argc, char* argv[]) {
printf("CPU Brand Name: %d\n", detect::cpu_brand());
printf("CPU ID: %d\n", detect::cpu_id());
printf("CPU Hypervisor bit: %d\n", detect::cpu_hypervisor_bit());
printf("BIOS Manufacturer: %d\n", detect::bios_manufacturer());
printf("BIOS Version: %d\n", detect::bios_version());
printf("Scren resolution: %d\n", detect::screen_resolution());
printf("Memory amount: %d\n", detect::memory_amount());
printf("CPU cores: %d\n", detect::cpu_cores());
printf("Disk space: %d\n", detect::disk_space());
}
+35
View File
@@ -0,0 +1,35 @@
#include "utils.hpp"
#include "detectors.hpp"
#include <string>
#include <codecvt>
#include <vector>
std::string utils::bstr_to_str(BSTR bstr) {
if (!bstr) return std::string("");
std::wstring ws(bstr, SysStringLen(bstr));
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::string narrow = converter.to_bytes(ws);
return narrow;
}
std::string utils::lowercase(std::string str) {
std::string result = "";
for (auto c : str) {
result += std::tolower(c);
}
return result;
}
bool utils::str_includes(std::string str, std::vector<std::string> includes) {
for (auto i : includes) {
if (str.find(i) != std::string::npos) {
return true;
}
}
return false;
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <string>
#include <vector>
#include <wtypes.h>
namespace utils {
std::string bstr_to_str(BSTR bstr);
std::string lowercase(std::string str);
bool str_includes(std::string str, std::vector<std::string> includes);
};
+181
View File
@@ -0,0 +1,181 @@
#define _WIN32_DCOM
#include <iostream>
using namespace std;
#include <comdef.h>
#include <Wbemidl.h>
#include <string>
#include "wmi.hpp"
#pragma comment(lib, "wbemuuid.lib")
int wmi::get_wmi(std::string wql_query, std::function<void(IWbemClassObject*, VARIANT*)> get_property) {
HRESULT hres;
// Step 1: --------------------------------------------------
// Initialize COM. ------------------------------------------
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
cout << "Failed to initialize COM library. Error code = 0x"
<< hex << hres << endl;
return 1; // 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();
return 1; // 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();
return 1; // 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
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();
return 1; // Program has failed.
}
// 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();
return 1; // 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(wql_query.c_str()),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
cout << "WMI query failed." << " Error code = 0x" << hex << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return 1; // 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)
{
break;
}
VARIANT vtProp;
VariantInit(&vtProp);
// Get the value of the property
get_property(pclsObj, &vtProp);
//hr = pclsObj->Get(L"SMBIOSBIOSVersion", 0, &vtProp, 0, 0);
//wcout << "SMBIOSBIOSVersion : " << vtProp.bstrVal << endl;
VariantClear(&vtProp);
pclsObj->Release();
}
// Cleanup
// ========
pSvc->Release();
pLoc->Release();
pEnumerator->Release();
CoUninitialize();
return 0; // Program successfully completed.
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <string>
#include <Wbemidl.h>
#include <functional>
namespace wmi {
int get_wmi(std::string wql_query, std::function<void(IWbemClassObject*, VARIANT*)> get_property);
};