Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added checks for cpu model spoofing #242

Merged
merged 1 commit into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ You can view the full docs [here](docs/documentation.md). All the details such a

> I would've made it strictly MIT so proprietary software can make use of the library, but some of the techniques employed are from GPL 3.0 projects, and I have no choice but to use the same license for legal reasons.
>
> This gave me an idea to make an MIT version without all of the GPL code so it can also be used without forcing your code to be open-source. It should be noted that the MIT version removes <b>9</b> techniques out of 128 (as of 2.0 version), and the lesser the number of techniques, the less accurate the overall result might be.
> This gave me an idea to make an MIT version without all of the GPL code so it can also be used without forcing your code to be open-source. It should be noted that the MIT version removes <b>9</b> techniques out of 126 (as of 2.0 version), and the lesser the number of techniques, the less accurate the overall result might be.

</details>

Expand Down
4 changes: 2 additions & 2 deletions docs/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ VMAware provides a convenient way to not only check for VMs, but also have the f
| `VM::MUTEX` | Check for mutex strings of VM brands | Windows | 85% | | | | |
| `VM::ODD_CPU_THREADS` | Check for odd CPU threads, usually a sign of modification through VM setting because 99% of CPUs have even numbers of threads | | 80% | | | | |
| `VM::INTEL_THREAD_MISMATCH` | Check for Intel CPU thread count database if it matches the system's thread count | | 100% | | | | |
| `VM::XEON_THREAD_MISMATCH` | Same as above, but for Xeon Intel CPUs | | 85% | | | | |
| `VM::XEON_THREAD_MISMATCH` | Same as above, but for Xeon Intel CPUs | | 100% | | | | |
| `VM::NETTITUDE_VM_MEMORY` | Check for memory regions to detect VM-specific brands | Windows | 100% | | | | |
| `VM::CPUID_BITSET` | Check for CPUID technique by checking whether all the bits equate to more than 4000 | | 25% | | | | |
| `VM::CUCKOO_DIR` | Check for cuckoo directory using crt and WIN API directory functions | Windows | 30% | | | | |
Expand Down Expand Up @@ -533,7 +533,7 @@ This is the table of all the brands the lib supports.
| AMD SEV-SNP | `VM::brands::AMD_SEV_SNP` | VM encryptor | |
| Neko Project II | `VM::brands::NEKO_PROJECT` | Emulator | |
| Google Compute Engine (KVM) | `VM::brands::GCE` | Cloud VM service | |
| NoirVisor | `VM::brands::NOIRVISOR` | Cloud VM service | |
| NoirVisor | `VM::brands::NOIRVISOR` | Hypervisor (type 1) | |
| Qihoo 360 Sandbox | `VM::brands::QIHOO` | Sandbox | |

<br>
Expand Down
2 changes: 1 addition & 1 deletion src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
|------|---------|
| `cli.cpp` | Entire CLI tool code |
| `vmaware.hpp` | Official and original library header in GPL-3.0, most likely what you're looking for. |
| `vmaware_MIT.hpp` | Same as above but in MIT. But this removes 9 techniques out of 120 |
| `vmaware_MIT.hpp` | Same as above but in MIT. But this removes 9 techniques out of 126 |


> [!IMPORTANT]
Expand Down
1 change: 1 addition & 0 deletions src/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ std::string vm_description(const std::string& vm_brand) {
{ VM::brands::AMD_SEV_SNP, "AMD SEV-Secure Nested Paging (SEV-SNP) adds memory integrity protection to SEV-ES. Uses reverse map tables (RMP) to prevent hypervisor-mediated replay/spoofing attacks. Enables attested launch for cloud workloads via guest policy certificates and AMD's Key Distribution Service (KDS)." },
{ VM::brands::NEKO_PROJECT, "Neko Project II is an emulator designed for emulating PC-98 computers. They are a lineup of Japanese 16-bit and 32-bit personal computers manufactured by NEC from 1982 to 2003. While based on Intel processors, it uses an in-house architecture making it incompatible with IBM clones." },
{ VM::brands::NOIRVISOR, "NoirVisor is a hardware-accelerated hypervisor with support to complex functions and purposes. It is designed to support processors based on x86 architecture with hardware-accelerated virtualization feature. For example, Intel processors supporting Intel VT-x or AMD processors supporting AMD-V meet the requirement. It was made by Zero-Tang." },
{ VM::brands::QIHOO, "360 sandbox is a part of 360 Total Security. Similar to other sandbox software, it provides a virtualized environment where potentially malicious or untrusted programs can run without affecting the actual system. Qihoo 360 Sandbox is commonly used for testing unknown applications, analyzing malware behavior, and protecting users from zero-day threats." },
{ VM::brands::NULL_BRAND, "Indicates no detectable virtualization brand. This result may occur on bare-metal systems, unsupported/obscure hypervisors, or when anti-detection techniques (e.g., VM escaping) are employed by the guest environment." }
};

Expand Down
124 changes: 106 additions & 18 deletions src/vmaware.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2677,7 +2677,7 @@ struct VM {


/**
* @brief Checks if the number of logical processors obtained by various methods match.
* @brief Checks if the number of logical processors obtained by various methods match
*
* This function retrieves the number of logical processors in the system using several different
* methods and compares them to ensure consistency. The methods include:
Expand All @@ -2696,10 +2696,11 @@ struct VM {
* - NtQuerySystemInformation with SystemProcessorPerformanceInformation (processor performance info)
* - NUMA API functions (GetNumaHighestNodeNumber and GetNumaNodeProcessorMaskEx) to enumerate processors by NUMA node
* - Enumeration of processor groups via GetActiveProcessorGroupCount and GetActiveProcessorCount for each group
* - Dynamically testing each available processor by setting the thread affinity mask
*
* @return bool true if there is a mismatch in thread counts from different methods, false otherwise.
* @return bool false if there is a mismatch in thread counts from different methods, true otherwise
*/
[[nodiscard]] static bool does_threadcount_mismatch() {
[[nodiscard]] static bool verify_thread_data() {
#pragma warning (disable : 4191) // supress useless warnings about unsafe conversions from 'FARPROC' to 'VM::util::does_threadcount_mismatch::<lambda_X>
auto GetThreadsUsingGetLogicalProcessorInformationEx = []() -> int {
DWORD bufferSize = 0;
Expand Down Expand Up @@ -2979,6 +2980,31 @@ struct VM {
}
return totalCount;
};

auto GetThreadsUsingAffinityTest = []() -> int {
DWORD_PTR originalMask = 0;
if (!GetProcessAffinityMask(GetCurrentProcess(), &originalMask, &originalMask)) {
return 0;
}
if (originalMask == 0) {
return 0;
}
int count = 0;
DWORD_PTR thread = reinterpret_cast<DWORD_PTR>(GetCurrentThread());
DWORD_PTR savedMask = SetThreadAffinityMask(GetCurrentThread(), originalMask);

for (int bit = 0; bit < static_cast<int>(sizeof(DWORD_PTR) * 8); ++bit) {
DWORD_PTR testMask = (DWORD_PTR(1) << bit);
if (originalMask & testMask) {
DWORD_PTR previous = SetThreadAffinityMask(GetCurrentThread(), testMask);
if (previous != 0) {
count++;
}
}
}
SetThreadAffinityMask(GetCurrentThread(), originalMask);
return count;
};
#pragma warning (default : 4191)

const int wmiThreads = GetThreadsUsingWMI();
Expand All @@ -2996,8 +3022,9 @@ struct VM {
const int ntProcPerfThreads = GetThreadsUsingNtQueryProcessorPerformanceInformation();
const int numaApisThreads = GetThreadsUsingNumaAPIs();
const int processorGroupsThreads = GetThreadsUsingProcessorGroupsEnumeration();
const int affinityTestThreads = GetThreadsUsingAffinityTest();
std::vector<int> validThreads;
validThreads.reserve(15);
validThreads.reserve(16);

if (osThreads > 0) validThreads.push_back(osThreads);
if (wmiThreads > 0) validThreads.push_back(wmiThreads);
Expand All @@ -3014,19 +3041,68 @@ struct VM {
if (ntProcPerfThreads > 0) validThreads.push_back(ntProcPerfThreads);
if (numaApisThreads > 0) validThreads.push_back(numaApisThreads);
if (processorGroupsThreads > 0) validThreads.push_back(processorGroupsThreads);
if (affinityTestThreads > 0) validThreads.push_back(affinityTestThreads);

if (validThreads.size() < 2) {
return false;
return true;
}

int first = validThreads[0];
for (const int threadCount : validThreads) {
if (threadCount != first) {
return true;
return false;
}
}

return false;
return true;
}


/**
* @brief Checks if the name of the CPU obtained by various methods match
*
* @return bool false if there is a mismatch in thread counts from different methods, true otherwise
*/
[[nodiscard]] static bool verify_cpu_data() {
std::vector<std::string> sources;

// 1. WMI Source
if (wmi::initialize()) {
wmi_result results = wmi::execute(
L"SELECT Name FROM Win32_Processor", { L"Name" }
);
if (!results.empty() && results[0].type == wmi::result_type::String) {
sources.push_back(results[0].strValue);
}
}

// 2. Registry ProcessorNameString
HKEY hKey;
char reg_name[512]{};
DWORD buf_size = sizeof(reg_name);
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
0, KEY_READ, &hKey) == ERROR_SUCCESS) {
if (RegQueryValueExA(hKey, "ProcessorNameString",
nullptr, nullptr, (LPBYTE)reg_name, &buf_size) == ERROR_SUCCESS) {
sources.push_back(reg_name);
}
RegCloseKey(hKey);
}

// 3. cpuid
const std::string cpuid_model = cpu::get_brand();
sources.push_back(cpuid_model);

if (sources.empty()) return false;

const std::string& first = sources[0];
for (const auto& source : sources) {
if (source != first) {
return false;
}
}
return true;
}

/**
Expand Down Expand Up @@ -5841,8 +5917,12 @@ struct VM {
}

#if (WINDOWS)
if (util::does_threadcount_mismatch()) {
debug("INTEL_THREAD_MISMATCH: thread count sources mismatch");
if (!util::verify_thread_data()) {
debug("INTEL_THREAD_MISMATCH: Thread tampering detected");
return true;
}
if (!util::verify_cpu_data()) {
debug("INTEL_THREAD_MISMATCH: CPU model tampering detected");
return true;
}
#endif
Expand Down Expand Up @@ -6848,12 +6928,16 @@ struct VM {

debug("XEON_THREAD_MISMATCH: CPU model = ", model.string);

#if (WINDOWS)
if (util::does_threadcount_mismatch()) {
debug("INTEL_THREAD_MISMATCH: Thread tampering detected");
return false;
}
#endif
#if (WINDOWS)
if (!util::verify_thread_data()) {
debug("INTEL_THREAD_MISMATCH: Thread tampering detected");
return true;
}
if (!util::verify_cpu_data()) {
debug("INTEL_THREAD_MISMATCH: CPU model tampering detected");
return true;
}
#endif

std::map<const char*, int> thread_database = {
// Xeon D
Expand Down Expand Up @@ -10005,8 +10089,12 @@ static bool rdtsc() {
}

#if (WINDOWS)
if (util::does_threadcount_mismatch()) {
debug("AMD_THREAD_MISMATCH: Thread tampering detected");
if (!util::verify_thread_data()) {
debug("INTEL_THREAD_MISMATCH: Thread tampering detected");
return true;
}
if (!util::verify_cpu_data()) {
debug("INTEL_THREAD_MISMATCH: CPU model tampering detected");
return true;
}
#endif
Expand Down Expand Up @@ -12422,7 +12510,7 @@ std::pair<VM::enum_flags, VM::core::technique> VM::core::technique_list[] = {
{ VM::MUTEX, { 85, VM::mutex, false } },
{ VM::ODD_CPU_THREADS, { 80, VM::odd_cpu_threads, false } },
{ VM::INTEL_THREAD_MISMATCH, { 100, VM::intel_thread_mismatch, false } },
{ VM::XEON_THREAD_MISMATCH, { 85, VM::xeon_thread_mismatch, false } },
{ VM::XEON_THREAD_MISMATCH, { 100, VM::xeon_thread_mismatch, false } },
{ VM::NETTITUDE_VM_MEMORY, { 100, VM::nettitude_vm_memory, false } },
{ VM::CPUID_BITSET, { 25, VM::cpuid_bitset, false } },
{ VM::CUCKOO_DIR, { 30, VM::cuckoo_dir, true } },
Expand Down