Windows C++ / Odd Thread Count
Author | kernelwernel |
Platform | Windows |
Language | C++ |
Technique | Odd Thread Count |
Code
/**
* The thread count of the CPU must be an even number, which allows
* us to detect whether the thread count has been modified through
* the VM creation stage. If the thread count is not an even number,
* this is a sign of thread tampering (with a few exceptions which
* are already covered).
*
* This code snippet is from the VMAware project at https://github.com/kernelwernel/VMAware
* detection made by kernelwernel, 2024
*/
#if (defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64) || defined(__MINGW32__))
#define MSVC 1
#define LINUX 0
#elif (defined(__linux__))
#define MSVC 0
#define LINUX 1
#else
#define MSVC 0
#define LINUX 0
#endif
#if (LINUX)
#include <cpuid.h>
#elif (MSVC)
#include <intrin.h>
#endif
#include <cstdint>
#include <thread>
#include <array>
#include <iostream>
/*
* @brief Check for odd CPU threads, usually a sign of modification through VM setting because 99% of CPUs have even numbers of threads
*/
bool odd_cpu_threads() {
struct stepping_struct {
std::uint8_t model;
std::uint8_t family;
std::uint8_t extmodel;
};
struct stepping_struct steps {};
// cross-platform wrapper function for linux and MSVC cpuid
auto cpuid = [](
std::uint32_t& a,
std::uint32_t& b,
std::uint32_t& c,
std::uint32_t& d,
const std::uint32_t a_leaf
) -> void {
#if (MSVC)
std::int32_t x[4]{};
__cpuid((std::int32_t*)x, static_cast<std::int32_t>(a_leaf));
a = static_cast<std::uint32_t>(x[0]);
b = static_cast<std::uint32_t>(x[1]);
c = static_cast<std::uint32_t>(x[2]);
d = static_cast<std::uint32_t>(x[3]);
#elif (LINUX)
__get_cpuid(a_leaf, &a, &b, &c, &d);
#else
return;
#endif
};
auto is_intel = [&]() -> bool {
constexpr std::uint32_t intel_ecx = 0x6c65746e;
std::uint32_t unused, ecx = 0;
cpuid(unused, unused, ecx, unused, 0);
return (ecx == intel_ecx);
};
auto is_amd = [&]() -> bool {
constexpr std::uint32_t amd_ecx = 0x69746e65;
std::uint32_t unused, ecx = 0;
cpuid(unused, unused, ecx, unused, 0);
return (ecx == amd_ecx);
};
std::uint32_t unused, eax = 0;
cpuid(eax, unused, unused, unused, 1);
steps.model = ((eax >> 4) & 0b1111);
steps.family = ((eax >> 8) & 0b1111);
steps.extmodel = ((eax >> 16) & 0b1111);
// check if the CPU is an intel celeron
auto is_celeron = [&]() -> bool {
if (!is_intel()) {
return false;
}
constexpr std::uint8_t celeron_family = 0x6;
constexpr std::uint8_t celeron_extmodel = 0x2;
constexpr std::uint8_t celeron_model = 0xA;
return (
steps.family == celeron_family &&
steps.extmodel == celeron_extmodel &&
steps.model == celeron_model
);
};
// check if the microarchitecture was made before 2006, which was around the time multi-core processors were implemented
auto old_microarchitecture = [&]() -> bool {
constexpr std::array<std::array<std::uint8_t, 3>, 32> old_archs = {{
// 80486
{{ 0x4, 0x0, 0x1 }},
{{ 0x4, 0x0, 0x2 }},
{{ 0x4, 0x0, 0x3 }},
{{ 0x4, 0x0, 0x4 }},
{{ 0x4, 0x0, 0x5 }},
{{ 0x4, 0x0, 0x7 }},
{{ 0x4, 0x0, 0x8 }},
{{ 0x4, 0x0, 0x9 }},
// P5
{{ 0x5, 0x0, 0x1 }},
{{ 0x5, 0x0, 0x2 }},
{{ 0x5, 0x0, 0x4 }},
{{ 0x5, 0x0, 0x7 }},
{{ 0x5, 0x0, 0x8 }},
// P6
{{ 0x6, 0x0, 0x1 }},
{{ 0x6, 0x0, 0x3 }},
{{ 0x6, 0x0, 0x5 }},
{{ 0x6, 0x0, 0x6 }},
{{ 0x6, 0x0, 0x7 }},
{{ 0x6, 0x0, 0x8 }},
{{ 0x6, 0x0, 0xA }},
{{ 0x6, 0x0, 0xB }},
// Netburst
{{ 0xF, 0x0, 0x6 }},
{{ 0xF, 0x0, 0x4 }},
{{ 0xF, 0x0, 0x3 }},
{{ 0xF, 0x0, 0x2 }},
{{ 0xF, 0x0, 0x10 }},
{{ 0x6, 0x1, 0x5 }}, // Pentium M (Talopai)
{{ 0x6, 0x1, 0x6 }}, // Core (Client)
{{ 0x6, 0x0, 0x9 }}, // Pentium M
{{ 0x6, 0x0, 0xD }}, // Pentium M
{{ 0x6, 0x0, 0xE }}, // Modified Pentium M
{{ 0x6, 0x0, 0xF }} // Core (Client)
}};
constexpr std::uint8_t FAMILY = 0;
constexpr std::uint8_t EXTMODEL = 1;
constexpr std::uint8_t MODEL = 2;
for (const auto& arch : old_archs) {
if (
steps.family == arch.at(FAMILY) &&
steps.extmodel == arch.at(EXTMODEL) &&
steps.model == arch.at(MODEL)
) {
return true;
}
}
return false;
};
// if neither AMD or Intel, return false
if (!(is_intel() || is_amd())) {
return false;
}
// Intel Celeron CPUs are relatively modern, but they can contain a single or odd thread count
if (is_celeron()) {
return false;
}
// CPUs before 2006 had no official multi-core processors (for both AMD and Intel)
if (old_microarchitecture()) {
return false;
}
// Is the thread count odd?
return (std::thread::hardware_concurrency() & 1);
}
int main() {
std::cout << "\nIs the thread count odd? = " << (odd_cpu_threads() ? "Yes, very likely a VM" : "No, could be baremetal") << "\n";
return 0;
}
Created
August 17, 2024
Last Revised
August 17, 2024