Skip to content

Commit 146c905

Browse files
authored
Merge pull request #339 from kernelwernel/dev
Addressed critical performance bottlenecks
2 parents eb15509 + 08cf38d commit 146c905

2 files changed

Lines changed: 238 additions & 155 deletions

File tree

src/vmaware.hpp

Lines changed: 117 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@
2727
*
2828
*
2929
* ============================== SECTIONS ==================================
30-
* - enums for publicly accessible techniques => line 550
31-
* - struct for internal cpu operations => line 741
32-
* - struct for internal memoization => line 1212
33-
* - struct for internal utility functions => line 1336
34-
* - struct for internal core components => line 10031
35-
* - start of VM detection technique list => line 2337
36-
* - start of public VM detection functions => line 10695
37-
* - start of externally defined variables => line 11641
30+
* - enums for publicly accessible techniques => line 551
31+
* - struct for internal cpu operations => line 742
32+
* - struct for internal memoization => line 1213
33+
* - struct for internal utility functions => line 1337
34+
* - struct for internal core components => line 10067
35+
* - start of VM detection technique list => line 2363
36+
* - start of public VM detection functions => line 10731
37+
* - start of externally defined variables => line 11677
3838
*
3939
*
4040
* ============================== EXAMPLE ===================================
@@ -1408,7 +1408,7 @@ struct VM {
14081408
#endif
14091409
}
14101410

1411-
#if defined(WINDOWS) && (defined(UNICODE) || defined(_UNICODE))
1411+
#if (WINDOWS) && (defined(UNICODE) || defined(_UNICODE))
14121412
// handle TCHAR conversion
14131413
[[nodiscard]] static bool exists(const TCHAR* path) {
14141414
char c_szText[_MAX_PATH]{};
@@ -1743,7 +1743,7 @@ struct VM {
17431743
#endif
17441744
}
17451745

1746-
// et available memory space
1746+
// get available memory space
17471747
[[nodiscard]] static u64 get_memory_space() {
17481748
#if (WINDOWS)
17491749
MEMORYSTATUSEX statex = { 0 };
@@ -1872,6 +1872,32 @@ struct VM {
18721872
#endif
18731873
}
18741874

1875+
// Returns a list of running process names
1876+
[[nodiscard]] static std::unordered_set<std::string> get_running_process_names() {
1877+
std::unordered_set<std::string> processNames;
1878+
#if (WINDOWS)
1879+
DWORD processes[1024], bytesReturned;
1880+
1881+
if (!K32EnumProcesses(processes, sizeof(processes), &bytesReturned)) {
1882+
return processNames;
1883+
}
1884+
1885+
DWORD numProcesses = bytesReturned / sizeof(DWORD);
1886+
char processName[MAX_PATH];
1887+
1888+
for (DWORD i = 0; i < numProcesses; ++i) {
1889+
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processes[i]);
1890+
if (hProcess != nullptr) {
1891+
if (K32GetModuleBaseNameA(hProcess, nullptr, processName, sizeof(processName))) {
1892+
processNames.insert(processName);
1893+
}
1894+
CloseHandle(hProcess);
1895+
}
1896+
}
1897+
#endif
1898+
return processNames;
1899+
}
1900+
18751901
// Retrieves the computer name
18761902
[[nodiscard]] static std::string get_hostname() {
18771903
#if (WINDOWS)
@@ -1892,13 +1918,12 @@ struct VM {
18921918
return std::string();
18931919
}
18941920

1895-
18961921
/**
18971922
* @brief Checks whether the system is running in a Hyper-V virtual machine or if the host system has Hyper-V enabled
18981923
* @note Hyper-V's presence on a host system can set certain hypervisor-related CPU flags that may appear similar to those in a virtualized environment, which can make it challenging to differentiate between an actual Hyper-V virtual machine (VM) and a host system with Hyper-V enabled.
18991924
* This can lead to false conclusions, where the system might mistakenly be identified as running in a Hyper-V VM, when in reality, it's simply the host system with Hyper-V features active.
19001925
* This check aims to distinguish between these two cases by identifying specific CPU flags and hypervisor-related artifacts that are indicative of a Hyper-V VM rather than a host system with Hyper-V enabled.
1901-
* @author idea by Requiem (https://github.com/NotRequiem)
1926+
* @author Requiem (https://github.com/NotRequiem)
19021927
* @returns hyperx_state enum indicating the detected state:
19031928
* - HYPERV_ARTIFACT_VM for host with Hyper-V enabled
19041929
* - HYPERV_REAL_VM for real Hyper-V VM
@@ -2482,7 +2507,7 @@ struct VM {
24822507

24832508
/**
24842509
* @brief Check if mac address starts with certain VM designated values
2485-
* @category All systems (I think)
2510+
* @category Linux and Windows
24862511
* @implements VM::MAC
24872512
*/
24882513
[[nodiscard]] static bool mac_address_check() {
@@ -2532,30 +2557,19 @@ struct VM {
25322557
debug("MAC: ", "not successful");
25332558
}
25342559
#elif (WINDOWS)
2535-
PIP_ADAPTER_INFO AdapterInfo;
2536-
DWORD dwBufLen = sizeof(IP_ADAPTER_INFO);
2537-
2538-
AdapterInfo = (IP_ADAPTER_INFO*)std::malloc(sizeof(IP_ADAPTER_INFO));
2539-
2540-
if (AdapterInfo == NULL) {
2560+
DWORD dwBufLen = 0;
2561+
if (GetAdaptersInfo(nullptr, &dwBufLen) != ERROR_BUFFER_OVERFLOW) {
25412562
return false;
25422563
}
25432564

2544-
if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == ERROR_BUFFER_OVERFLOW) {
2545-
std::free(AdapterInfo);
2546-
AdapterInfo = (IP_ADAPTER_INFO*)std::malloc(dwBufLen);
2547-
if (AdapterInfo == NULL) {
2548-
return false;
2549-
}
2565+
PIP_ADAPTER_INFO AdapterInfo = (PIP_ADAPTER_INFO)std::malloc(dwBufLen);
2566+
if (AdapterInfo == nullptr) {
2567+
return false;
25502568
}
25512569

25522570
if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == NO_ERROR) {
2553-
PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo;
2554-
for (std::size_t i = 0; i < 6; i++) {
2555-
mac[i] = pAdapterInfo->Address[i];
2556-
}
2571+
std::memcpy(mac, AdapterInfo->Address, sizeof(mac));
25572572
}
2558-
25592573
std::free(AdapterInfo);
25602574
#else
25612575
return false;
@@ -2574,32 +2588,28 @@ struct VM {
25742588
*/
25752589
debug("MAC: ", ss.str());
25762590
#endif
2577-
25782591
// better expression to fix code duplication
2579-
auto compare = [=](const u8 mac1, const u8 mac2, const u8 mac3) noexcept -> bool {
2592+
auto compare = [mac](u8 mac1, u8 mac2, u8 mac3) noexcept -> bool {
25802593
return (mac[0] == mac1 && mac[1] == mac2 && mac[2] == mac3);
2581-
};
2594+
};
25822595

2583-
if (compare(0x08, 0x00, 0x27)) {
2596+
// Check for known virtualization MAC address prefixes
2597+
if (compare(0x08, 0x00, 0x27))
25842598
return core::add(brands::VBOX);
2585-
}
25862599

2587-
if (
2588-
(compare(0x00, 0x0C, 0x29)) ||
2589-
(compare(0x00, 0x1C, 0x14)) ||
2590-
(compare(0x00, 0x50, 0x56)) ||
2591-
(compare(0x00, 0x05, 0x69))
2592-
) {
2600+
if (compare(0x00, 0x0C, 0x29) ||
2601+
compare(0x00, 0x1C, 0x14) ||
2602+
compare(0x00, 0x50, 0x56) ||
2603+
compare(0x00, 0x05, 0x69))
2604+
{
25932605
return core::add(brands::VMWARE);
25942606
}
25952607

2596-
if (compare(0x00, 0x16, 0xE3)) {
2608+
if (compare(0x00, 0x16, 0xE3))
25972609
return core::add(brands::XEN);
2598-
}
25992610

2600-
if (compare(0x00, 0x1C, 0x42)) {
2611+
if (compare(0x00, 0x1C, 0x42))
26012612
return core::add(brands::PARALLELS);
2602-
}
26032613

26042614
/*
26052615
see https://github.com/kernelwernel/VMAware/issues/105
@@ -3396,17 +3406,14 @@ struct VM {
33963406
#if (!WINDOWS)
33973407
return false;
33983408
#else
3399-
DWORD pnsize = 0x1000;
3400-
char provider[0x1000];
3401-
3402-
DWORD retv = WNetGetProviderNameA(WNNC_NET_RDR2SAMPLE, provider, &pnsize);
3403-
bool result = false;
3409+
char provider[256];
3410+
DWORD pnsize = sizeof(provider);
3411+
const DWORD retv = WNetGetProviderNameA(WNNC_NET_RDR2SAMPLE, provider, &pnsize);
34043412

3405-
if (retv == NO_ERROR) {
3406-
result = (strcmp(provider, "VirtualBox Shared Folders") == 0);
3407-
}
3413+
if (retv != NO_ERROR)
3414+
return false;
34083415

3409-
return result;
3416+
return (strncmp(provider, "VirtualBox Shared Folders", 26) == 0);
34103417
#endif
34113418
}
34123419

@@ -3651,41 +3658,43 @@ struct VM {
36513658
#if (!WINDOWS)
36523659
return false;
36533660
#else
3654-
if (util::is_proc_running("joeboxserver.exe") || util::is_proc_running("joeboxcontrol.exe")) {
3661+
const auto runningProcesses = util::get_running_process_names();
3662+
3663+
if (runningProcesses.count("joeboxserver.exe") || runningProcesses.count("joeboxcontrol.exe")) {
36553664
debug("VM_PROCESSES: Detected JoeBox process.");
36563665
return core::add(brands::JOEBOX);
36573666
}
36583667

3659-
if (util::is_proc_running("prl_cc.exe") || util::is_proc_running("prl_tools.exe")) {
3668+
if (runningProcesses.count("prl_cc.exe") || runningProcesses.count("prl_tools.exe")) {
36603669
debug("VM_PROCESSES: Detected Parallels process.");
36613670
return core::add(brands::PARALLELS);
36623671
}
36633672

3664-
if (util::is_proc_running("vboxservice.exe") || util::is_proc_running("vboxtray.exe")) {
3673+
if (runningProcesses.count("vboxservice.exe") || runningProcesses.count("vboxtray.exe")) {
36653674
debug("VM_PROCESSES: Detected VBox process.");
36663675
return core::add(brands::VBOX);
36673676
}
36683677

3669-
if (util::is_proc_running("vmsrvc.exe") || util::is_proc_running("vmusrvc.exe")) {
3678+
if (runningProcesses.count("vmsrvc.exe") || runningProcesses.count("vmusrvc.exe")) {
36703679
debug("VM_PROCESSES: Detected VPC process.");
36713680
return core::add(brands::VPC);
36723681
}
36733682

3674-
if (util::is_proc_running("xenservice.exe") || util::is_proc_running("xsvc_depriv.exe")) {
3683+
if (runningProcesses.count("xenservice.exe") || runningProcesses.count("xsvc_depriv.exe")) {
36753684
debug("VM_PROCESSES: Detected Xen process.");
36763685
return core::add(brands::XEN);
36773686
}
36783687

3679-
if (util::is_proc_running("vm3dservice.exe") ||
3680-
util::is_proc_running("VGAuthService.exe") ||
3681-
util::is_proc_running("vmtoolsd.exe")) {
3688+
if (runningProcesses.count("vm3dservice.exe") ||
3689+
runningProcesses.count("VGAuthService.exe") ||
3690+
runningProcesses.count("vmtoolsd.exe")) {
36823691
debug("VM_PROCESSES: Detected VMware process.");
36833692
return core::add(brands::VMWARE);
36843693
}
36853694

3686-
if (util::is_proc_running("vdagent.exe") ||
3687-
util::is_proc_running("vdservice.exe") ||
3688-
util::is_proc_running("qemuwmi.exe")) {
3695+
if (runningProcesses.count("vdagent.exe") ||
3696+
runningProcesses.count("vdservice.exe") ||
3697+
runningProcesses.count("qemuwmi.exe")) {
36893698
debug("VM_PROCESSES: Detected QEMU process.");
36903699
return core::add(brands::QEMU);
36913700
}
@@ -7900,6 +7909,33 @@ struct VM {
79007909
#ifdef __VMAWARE_DEBUG__
79017910
u64 totalCycles = 0;
79027911
#endif
7912+
char* flushBuffer = nullptr; // avoiding volatile on purpose
7913+
constexpr size_t kAlignment = 64;
7914+
constexpr size_t kBufferSize = static_cast<size_t>(64 * 1024) * 1024;
7915+
7916+
#if (WINDOWS)
7917+
#define COMPILER_BARRIER() _ReadWriteBarrier()
7918+
#else
7919+
#define COMPILER_BARRIER() __asm__ __volatile__("" ::: "memory")
7920+
#endif
7921+
7922+
#if (WINDOWS)
7923+
flushBuffer = (char*)_aligned_malloc(kBufferSize, kAlignment);
7924+
if (!flushBuffer) {
7925+
flushBuffer = new (std::nothrow) char[kBufferSize];
7926+
}
7927+
#elif (LINUX || APPLE)
7928+
int err = posix_memalign((void**)&flushBuffer, kAlignment, kBufferSize);
7929+
if (err != 0 || !flushBuffer) {
7930+
flushBuffer = new (std::nothrow) char[kBufferSize];
7931+
}
7932+
#else
7933+
// volatile char* flushBuffer = new volatile char[kBufferSize];
7934+
flushBuffer = new (std::nothrow) char[kBufferSize];
7935+
#endif
7936+
// Define a rotation scheme over segments. Here, we split the buffer into a number of segments
7937+
constexpr size_t segmentsCount = 8; // basically 1/8 of the buffer per iteration
7938+
constexpr size_t segmentSize = kBufferSize / segmentsCount;
79037939
int spikeCount = 0;
79047940
for (int i = 0; i < classicIterations; i++) {
79057941
u64 start = __rdtsc();
@@ -7920,21 +7956,27 @@ struct VM {
79207956
if (cycles >= classicThreshold) {
79217957
spikeCount++;
79227958
}
7923-
// to induce cache flushing
7924-
constexpr size_t bufferSize = static_cast<size_t>(64 * 1024) * 1024;
7925-
volatile char* flushBuffer = new volatile char[bufferSize];
7959+
// Instead of flushing the entire buffer every iteration (which would decrease performance a lot),
7960+
// flush only one segment per iteration
7961+
size_t segmentIndex = i % segmentsCount;
7962+
size_t offsetStart = segmentIndex * segmentSize;
7963+
size_t offsetEnd = offsetStart + segmentSize;
79267964

7927-
// better than thread sleeps
7928-
for (size_t j = 0; j < bufferSize; j += 64) {
7965+
// this detection works better when inducing cache flushing without thread sleeps
7966+
for (size_t j = offsetStart; j < offsetEnd; j += 64) {
79297967
flushBuffer[j] = static_cast<char>(j);
7930-
#if (x86 && (GCC || CLANG || MSVC))
7931-
_mm_clflush(const_cast<const void*>(
7932-
reinterpret_cast<const volatile void*>(&flushBuffer[j])));
7968+
#if defined(x86) && (defined(GCC) || defined(CLANG) || defined(MSVC))
7969+
COMPILER_BARRIER();
7970+
// _mm_clflushopt not available on some systems
7971+
_mm_clflush(reinterpret_cast<const void*>(&flushBuffer[j]));
79337972
#endif
79347973
}
7935-
7936-
delete[] flushBuffer;
79377974
}
7975+
#if (WINDOWS)
7976+
_aligned_free((void*)flushBuffer);
7977+
#else
7978+
free((void*)flushBuffer);
7979+
#endif
79387980

79397981
#ifdef __VMAWARE_DEBUG__
79407982
const double averageCycles = static_cast<double>(totalCycles) / classicIterations;

0 commit comments

Comments
 (0)