.
+MIT License
+
+Copyright (c) 2024 kernelwernel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index a05d1cd..8a68397 100644
--- a/README.md
+++ b/README.md
@@ -141,7 +141,7 @@ You can view the full docs [here](docs/documentation.md). All the details such a
> Hyper-V has an obscure feature where if it's enabled in the host system, the CPU hardware values makes it look like the whole system is running inside Hyper-V, which isn't true. This makes it a challenge to determine whether the hardware values the library is collecting is either a real Hyper-V VM, or just the artifacts of what Hyper-V has left as a consequence of having it enabled in the host system. The reason why this is a problem is because the library might falsely conclude that your the host system is running in Hyper-V, which is a false positive. This is where the **Hyper-X** mechanism comes into play to distinguish between these two. This was designed by Requiem
-
+
diff --git a/TODO.md b/TODO.md
index e123e49..8b5bfa8 100644
--- a/TODO.md
+++ b/TODO.md
@@ -43,10 +43,17 @@
- [ ] make a medium post about it
- [ ] test the VM::modify_score() function
- [ ] check if bios date in /sys/class/dmi/id/ could be useful under QEMU
-- [ ] make the cli demo in the readme for the 1.8 version
-- [ ] fix the percentage thing for the disabled techniques
-- [ ] adopt the firmware technique from the vmprotect source code leak
+- [ ] make the cli demo in the readme for the 1.9 version
+- [X] fix the percentage thing for the disabled techniques
+- [X] adopt the firmware technique from the vmprotect source code leak
- [ ] update the Hyper-X graph with the cpu manufacturer part
+- [ ] add a .so, .dll, and .dylib shared object files in the release
+- [X] make a struct version as an alternative
+- [X] add the license style like in ffmpeg https://github.com/FFmpeg/FFmpeg/tree/master?tab=License-1-ov-file
+- [ ] fix the issue of VM::QEMU_USB being ultra slow
+- [X] make a MIT transformer python script from GPL to MIT
+- [ ] /sys/class/dmi/id/product_name check this in qemu
+
# Distant plans
- add the library to conan.io when released
diff --git a/assets/Hyper-X_version_2.drawio b/assets/Hyper-X_version_2.drawio
new file mode 100644
index 0000000..dc53209
--- /dev/null
+++ b/assets/Hyper-X_version_2.drawio
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/Hyper-X_version_2.png b/assets/Hyper-X_version_2.png
new file mode 100644
index 0000000..afaefad
Binary files /dev/null and b/assets/Hyper-X_version_2.png differ
diff --git a/assets/demo.jpg b/assets/demo.jpg
index 939621d..f7e1ec9 100644
Binary files a/assets/demo.jpg and b/assets/demo.jpg differ
diff --git a/auxiliary/arg_checks.py b/auxiliary/arg_checks.py
deleted file mode 100644
index 65ba21c..0000000
--- a/auxiliary/arg_checks.py
+++ /dev/null
@@ -1,183 +0,0 @@
-#
-# ██╗ ██╗███╗ ███╗ █████╗ ██╗ ██╗ █████╗ ██████╗ ███████╗
-# ██║ ██║████╗ ████║██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔════╝
-# ██║ ██║██╔████╔██║███████║██║ █╗ ██║███████║██████╔╝█████╗
-# ╚██╗ ██╔╝██║╚██╔╝██║██╔══██║██║███╗██║██╔══██║██╔══██╗██╔══╝
-# ╚████╔╝ ██║ ╚═╝ ██║██║ ██║╚███╔███╔╝██║ ██║██║ ██║███████╗
-# ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝
-#
-# C++ VM detection library
-#
-# =============================================================
-#
-# This is just an internal script for CI/CD. The main goal is to
-# check whether all of the techniques are actually updated since
-# keeping track of the docs, the cli, and the table isn't easy,
-# so I'm automating the checks in case I forget to update any.
-#
-# ===============================================================
-#
-# - Made by: @kernelwernel (https://github.com/kernelwernel)
-# - Repository: https://github.com/kernelwernel/VMAware
-# - License: GPL 3.0
-
-import sys
-import re
-
-def fetch():
- # fetch file content
- with open("../src/vmaware.hpp", 'r') as vmaware:
- header_content = vmaware.readlines()
-
- # reversed since the table is at the very end of the vmaware.hpp file
- header_content.reverse()
-
- # breakpoint
- keyword = "const std::map VM::core::technique_table = {"
-
- # fetch index of breakpoint
- index_of_keyword = next((i for i, line in enumerate(header_content) if keyword in line), None)
-
- # remove everything before the breakpoint for simplification
- if index_of_keyword is not None:
- header_content = header_content[:index_of_keyword + 1]
-
- return header_content
-
-
-
-def filter(raw_content):
- trimmed_content = []
-
- # filter
- trimmed_content = [s for s in raw_content if not (
- s.isspace() or
- "//" in s or
- ";" in s or
- "VM::core::technique" in s
- )]
-
- # strip all whitespace
- trimmed_content = [s.strip() for s in trimmed_content]
-
- return trimmed_content
-
-
-
-def tokenize(trimmed_content):
- flag_array = []
-
- # pattern for VM::FLAG_EXAMPLE1
- pattern = r'\bVM::([A-Z0-9_]+)\b'
-
- # match and push to flag_array[]
- for line in trimmed_content:
- match = re.search(pattern, line)
-
- if match:
- flag_array.append(match.group(0))
- else:
- print("Unable to find flag variable for " + line)
- sys.exit(1)
-
- return flag_array
-
-
-
-def check_docs(flag_array):
- # fetch docs content
- with open("../docs/documentation.md", 'r') as docs:
- docs_content = docs.readlines()
-
- # strip whitespace
- docs_content = [s.strip() for s in docs_content]
-
- # find indices
- start = "# Flag table"
- end = "# Non-technique flags"
-
- # extract the indexes
- try:
- start_index = docs_content.index(start)
- end_index = docs_content.index(end)
- except ValueError:
- print("Couldn't find range index point \"# Flag table\" or \"# Non-technique flags\"")
- start_index = end_index = None
- sys.exit(1)
-
- # extract the range between the aforementioned indexes
- if start_index is not None and end_index is not None:
- extracted_range = docs_content[start_index + 1:end_index]
- docs_content = extracted_range
-
- # filter elements with whitespace
- docs_content = [s for s in docs_content if not s.isspace() and s and "VM::" in s]
-
- # extract flag string for every line
- docs_flags = []
- pattern = r'`([^`]+)`'
- for line in docs_content:
- match = re.search(pattern, line)
-
- if match:
- docs_flags.append(match.group(1))
- else:
- print("Pattern not found in the line \"" + line + "\"")
- sys.exit(1)
-
- set1 = set(docs_flags)
- set2 = set(flag_array)
-
- # Check if every element in set1 has a corresponding element in set2
- all_elements_have_pair = set1.issubset(set2) and set2.issubset(set1)
-
- not_paired = set1.symmetric_difference(set2)
-
- if not_paired:
- print("Mismatched elements found in documentation.md and vmaware.hpp, make sure to include the technique in both files")
- print("Elements without a pair:", not_paired)
- sys.exit(1)
-
-
-
-
-def check_cli(flag_array):
- # fetch docs content
- with open("../src/cli.cpp", 'r') as cli:
- cli_content = cli.readlines()
-
- # strip whitespace
- cli_content = [s.strip() for s in cli_content]
-
- # filter elements with whitespace
- cli_content = [s for s in cli_content if ("checker(" in s)]
-
- # extract the flags
- cli_flags = []
- pattern = r'checker\((.*?),'
- for line in cli_content:
- match = re.search(pattern, line)
-
- if match:
- cli_flags.append(match.group(1).strip())
- else:
- print("Pattern not found in the string.")
-
- set1 = set(cli_flags)
- set2 = set(flag_array)
-
- # check if every element in set1 has a corresponding element in set2
- not_paired = set1.symmetric_difference(set2)
-
- if not_paired:
- print("Mismatched elements found in cli.cpp and vmaware.hpp, make sure to include the technique in both files")
- print("Elements without a pair:", not_paired)
- sys.exit(1)
-
-
-raw_content = fetch()
-trimmed_content = filter(raw_content)
-flags = tokenize(trimmed_content)
-
-check_docs(flags)
-check_cli(flags)
\ No newline at end of file
diff --git a/auxiliary/cpuid_fuzzer.c b/auxiliary/cpuid_fuzzer.c
index 92a804a..6548009 100644
--- a/auxiliary/cpuid_fuzzer.c
+++ b/auxiliary/cpuid_fuzzer.c
@@ -27,7 +27,6 @@
#include
#include
#include
-#include
#if (defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64) || defined(__MINGW32__))
#define MSVC 1
@@ -40,11 +39,13 @@
#endif
#if (LINUX)
- #define _GNU_SOURCE
#include
#include
#include
+ #include
#include
+#else
+ #include
#endif
// branching macros
@@ -76,6 +77,10 @@
#define proc_trace 0x00000014 // ecx = 0
#define aes 0x00000019
#define avx10 0x00000024 // ecx = 0
+#define vm0 0x40000000
+#define vm1 0x40000001
+#define vm2 0x40000002
+#define vm3 0x40000003
#define extended_proc_info 0x80000001
#define hypervisor 0x40000000
#define max_leaf 0x80000000
@@ -108,114 +113,6 @@
#define breakpoint 10000000
-typedef struct {
- void (*taskFunction)(void*); // Function pointer to the task
- void* arg; // Argument to the task function
-} Task;
-
-typedef struct {
- pthread_t* threads; // Array of thread IDs
- Task* taskQueue; // Array to hold tasks
- int queueSize; // Size of the task queue
- int nextTaskIndex; // Index to insert the next task
- int shutdown; // Flag to indicate pool shutdown
- pthread_mutex_t mutex; // Mutex for synchronization
- pthread_cond_t condition; // Condition variable for task availability
-} ThreadPool;
-
-// function executed by each thread in the pool
-void* threadFunction(void* arg) {
- ThreadPool* pool = (ThreadPool*)arg;
-
- while (1) {
- pthread_mutex_lock(&pool->mutex);
-
- while (pool->nextTaskIndex == 0 && !pool->shutdown) {
- pthread_cond_wait(&pool->condition, &pool->mutex);
- }
-
- if (pool->shutdown) {
- pthread_mutex_unlock(&pool->mutex);
- pthread_exit(NULL);
- }
-
- Task task = pool->taskQueue[--pool->nextTaskIndex];
-
- pthread_mutex_unlock(&pool->mutex);
-
- task.taskFunction(task.arg);
- }
-
- return NULL;
-}
-
-// initialize the thread pool
-ThreadPool* initializeThreadPool(int poolSize) {
- ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool));
- if (!pool) {
- perror("Error creating thread pool");
- exit(EXIT_FAILURE);
- }
-
- pool->threads = (pthread_t*)malloc(poolSize * sizeof(pthread_t));
- pool->taskQueue = (Task*)malloc(poolSize * sizeof(Task));
- pool->queueSize = poolSize;
- pool->nextTaskIndex = 0;
- pool->shutdown = 0;
-
- pthread_mutex_init(&pool->mutex, NULL);
- pthread_cond_init(&pool->condition, NULL);
-
- for (int i = 0; i < poolSize; i++) {
- if (pthread_create(&pool->threads[i], NULL, threadFunction, (void*)pool) != 0) {
- perror("Error creating thread");
- exit(EXIT_FAILURE);
- }
- }
-
- return pool;
-}
-
-// submit a task to the thread pool
-void submitTask(ThreadPool* pool, void (*taskFunction)(void*), void* arg) {
- pthread_mutex_lock(&pool->mutex);
-
- if (pool->nextTaskIndex == pool->queueSize) {
- fprintf(stderr, "Task queue is full. Task not submitted.\n");
- pthread_mutex_unlock(&pool->mutex);
- return;
- }
-
- pool->taskQueue[pool->nextTaskIndex].taskFunction = taskFunction;
- pool->taskQueue[pool->nextTaskIndex].arg = arg;
- pool->nextTaskIndex++;
-
- pthread_cond_signal(&pool->condition);
-
- pthread_mutex_unlock(&pool->mutex);
-}
-
-// shutdown the thread pool
-void shutdownThreadPool(ThreadPool* pool) {
- pthread_mutex_lock(&pool->mutex);
-
- pool->shutdown = 1;
-
- pthread_cond_broadcast(&pool->condition);
-
- pthread_mutex_unlock(&pool->mutex);
-
- for (int i = 0; i < pool->queueSize; i++) {
- pthread_join(pool->threads[i], NULL);
- }
-
- free(pool->threads);
- free(pool->taskQueue);
- free(pool);
-}
-
-
-
// basic cpuid wrapper
static void cpuid
(
@@ -240,7 +137,7 @@ static uint32_t get_highest_leaf() {
// scan for predetermined leafs
void leaf_mode_fuzzer(const uint64_t p_max_leaf) {
uint32_t reg[4];
- const uint32_t leafs[36] = {
+ const uint32_t leafs[40] = {
manufacturer, proc_info, cache_tlb,
serial, topology, topology2,
management, extended, extended2,
@@ -252,10 +149,11 @@ void leaf_mode_fuzzer(const uint64_t p_max_leaf) {
brand2, brand3, L1_cache,
L2_cache, capabilities, virtual,
svm, enc_mem_cap, ext_info2,
- amd_easter_egg, centaur_ext, centaur_feature
+ amd_easter_egg, centaur_ext, centaur_feature,
+ vm0, vm1, vm2, vm3
};
- const size_t leaf_arr_size = (sizeof(leafs) / sizeof(leafs[0]));
+ const size_t leaf_arr_size = (sizeof(leafs) / sizeof(leafs[0]));
for (int i = 0; i < leaf_arr_size; i++) {
if (leafs[i] >= p_max_leaf) {
@@ -271,83 +169,14 @@ void leaf_mode_fuzzer(const uint64_t p_max_leaf) {
reg[edx]
)) {
printf("leaf = %d\n", i);
- printf("eax = %d\n", reg[eax]);
- printf("ebx = %d\n", reg[ebx]);
- printf("ecx = %d\n", reg[ecx]);
- printf("edx = %d\n\n", reg[edx]);
+ printf("eax = 0x%0X\n", reg[eax]);
+ printf("ebx = 0x%0X\n", reg[ebx]);
+ printf("ecx = 0x%0X\n", reg[ecx]);
+ printf("edx = 0x%0X\n\n", reg[edx]);
}
}
}
-/*
-atomic_int counter;
-
-void scan_mode_worker() {
- for (int i = 0; i < divisor; i++) {
- int x = start;
-
- for (; x < limit; x++) {
- uint32_t reg[4];
- cpuid(reg, x, null_leaf);
-
- if (unlikely(
- reg[eax] || \
- reg[ebx] || \
- reg[ecx] || \
- reg[edx]
- )) {
- printf("leaf = %d\n", i);
- printf("eax = %d\n", reg[eax]);
- printf("ebx = %d\n", reg[ebx]);
- printf("ecx = %d\n", reg[ecx]);
- printf("edx = %d\n\n", reg[edx]);
- fprintf(file, "%s\n", logMessage);
- }
- }
-
- const int percent = (((i + 1) * 100) / p_max_leaf);
-
- printf("[LOG] Reached eax leaf %d (%d%%)\n", atomic_load(&counter), percent);
- limit += breakpoint;
- start += breakpoint;
- }
-
-}
-
-// scan mode fuzzer
-void scan_mode_fuzzer(const uint64_t p_max_leaf, const int32_t thread_count) {
- uint32_t limit = breakpoint;
- uint32_t start = 0;
-
- const int32_t threads = get_nprocs();
-
- const uint32_t divisor = (uint32_t)(p_max_leaf / breakpoint);
- printf("divisor = %d\n", divisor);
-
- atomic_init(&counter, 0);
- ThreadPool* pool = initializeThreadPool(8);
-
- // Submit example tasks to the thread pool
- for (int i = 0; i < 10; i++) {
- int* taskNumber = (int*)malloc(sizeof(int));
- *taskNumber = i;
- submitTask(pool, exampleTask, (void*)taskNumber);
- }
-
- // Sleep to allow tasks to complete
- sleep(2);
-
- // Shutdown the thread pool
- shutdownThreadPool(pool);
-}
-
-
-void exampleTask(void* low_bound, void* upper_bound) {
- int taskNumber = *(int*)arg;
- printf("Task %d executed by thread %lu\n", taskNumber, pthread_self());
-}
-*/
-
int main(int argc, char *argv[]) {
uint8_t flags = 0;
@@ -357,8 +186,6 @@ int main(int argc, char *argv[]) {
} else if (argc == 2) {
if (strcmp(argv[2], "--leaf") == 0) {
flags |= leaf_mode;
- } else if (strcmp(argv[2], "--scan") == 0) {
- flags |= scan_mode;
} else {
printf("%s", "Unknown flag provided, aborting\n");
return 1;
diff --git a/auxiliary/updater.py b/auxiliary/updater.py
index 36abeba..d0fadb1 100644
--- a/auxiliary/updater.py
+++ b/auxiliary/updater.py
@@ -10,10 +10,21 @@
#
# ===============================================================
#
-# This is an internal script to update the VMAware
-# header file's banner automatically and much more reliably.
-# For example, it'll update the line numbers for the sections
-# header, and other basic information.
+# This is an internal script to update various stuff of the project:
+# 1. Check whether all of the techniques are actually updated since
+# keeping track of the docs, the CLI, and the table isn't easy,
+# so I'm automating the checks in case I forget to update any.
+#
+# 2. Update the line numbers for the sections header based on what
+# line they are located, so it's a (tiny) bit easier to understand
+# the structure of the headers for anybody reading it for the first
+# time, it's more of a guide to point which parts are this and that.
+#
+# 3. Convert the GPL file (vmaware.hpp) into an MIT file (vmaware_MIT.hpp).
+# In other words, it'll remove all the GPL code so that it qualifies
+# as MIT compliant.
+#
+# 4. Update the dates in the banner, example: "1.9 (Septmber 2024)"
#
# ===============================================================
#
@@ -22,8 +33,231 @@
# - License: GPL 3.0
+import sys
+import re
+from datetime import datetime
+
+
+def arg_check():
+ def fetch():
+ # fetch file content
+ with open("../src/vmaware.hpp", 'r') as vmaware:
+ header_content = vmaware.readlines()
+
+ # reversed since the table is at the very end of the vmaware.hpp file
+ header_content.reverse()
+
+ # breakpoint
+ keyword = "const std::map VM::core::technique_table = {"
+
+ # fetch index of breakpoint
+ index_of_keyword = next((i for i, line in enumerate(header_content) if keyword in line), None)
+
+ # remove everything before the breakpoint for simplification
+ if index_of_keyword is not None:
+ header_content = header_content[:index_of_keyword + 1]
+
+ return header_content
+
+
+
+ def filter(raw_content):
+ trimmed_content = []
+
+ # filter
+ trimmed_content = [s for s in raw_content if not (
+ s.isspace() or
+ "//" in s or
+ ";" in s or
+ "VM::core::technique" in s
+ )]
+
+ # strip all whitespace
+ trimmed_content = [s.strip() for s in trimmed_content]
+
+ return trimmed_content
+
+
+
+ def tokenize(trimmed_content):
+ flag_array = []
+
+ # pattern for VM::FLAG_EXAMPLE1
+ pattern = r'\bVM::([A-Z0-9_]+)\b'
+
+ # match and push to flag_array[]
+ for line in trimmed_content:
+ match = re.search(pattern, line)
+
+ if match:
+ flag_array.append(match.group(0))
+ else:
+ print("Unable to find flag variable for " + line)
+ sys.exit(1)
+
+ return flag_array
+
+
+
+ def check_docs(flag_array):
+ # fetch docs content
+ with open("../docs/documentation.md", 'r') as docs:
+ docs_content = docs.readlines()
+
+ # strip whitespace
+ docs_content = [s.strip() for s in docs_content]
+
+ # find indices
+ start = "# Flag table"
+ end = "# Non-technique flags"
+
+ # extract the indexes
+ try:
+ start_index = docs_content.index(start)
+ end_index = docs_content.index(end)
+ except ValueError:
+ print("Couldn't find range index point \"# Flag table\" or \"# Non-technique flags\"")
+ start_index = end_index = None
+ sys.exit(1)
+
+ # extract the range between the aforementioned indexes
+ if start_index is not None and end_index is not None:
+ extracted_range = docs_content[start_index + 1:end_index]
+ docs_content = extracted_range
+
+ # filter elements with whitespace
+ docs_content = [s for s in docs_content if not s.isspace() and s and "VM::" in s]
+
+ # extract flag string for every line
+ docs_flags = []
+ pattern = r'`([^`]+)`'
+ for line in docs_content:
+ match = re.search(pattern, line)
+
+ if match:
+ docs_flags.append(match.group(1))
+ else:
+ print("Pattern not found in the line \"" + line + "\"")
+ sys.exit(1)
+
+ set1 = set(docs_flags)
+ set2 = set(flag_array)
+
+ # Check if every element in set1 has a corresponding element in set2
+ all_elements_have_pair = set1.issubset(set2) and set2.issubset(set1)
+
+ not_paired = set1.symmetric_difference(set2)
+
+ if not_paired:
+ print("Mismatched elements found in documentation.md and vmaware.hpp, make sure to include the technique in both files")
+ print("Elements without a pair:", not_paired)
+ sys.exit(1)
+
+
+
+
+ def check_cli(flag_array):
+ # fetch docs content
+ with open("../src/cli.cpp", 'r') as cli:
+ cli_content = cli.readlines()
+
+ # strip whitespace
+ cli_content = [s.strip() for s in cli_content]
+
+ # filter elements with whitespace
+ cli_content = [s for s in cli_content if ("checker(" in s)]
+
+ # extract the flags
+ cli_flags = []
+ pattern = r'checker\((.*?),'
+ for line in cli_content:
+ match = re.search(pattern, line)
+
+ if match:
+ cli_flags.append(match.group(1).strip())
+ else:
+ print("Pattern not found in the string.")
+
+ set1 = set(cli_flags)
+ set2 = set(flag_array)
+
+ # check if every element in set1 has a corresponding element in set2
+ not_paired = set1.symmetric_difference(set2)
+
+ if not_paired:
+ print("Mismatched elements found in cli.cpp and vmaware.hpp, make sure to include the technique in both files")
+ print("Elements without a pair:", not_paired)
+ sys.exit(1)
+
+
+ raw_content = fetch()
+ trimmed_content = filter(raw_content)
+ flags = tokenize(trimmed_content)
+
+ check_docs(flags)
+ check_cli(flags)
+
+
+
+
+
+
+
+
+
+
+
+def update_MIT():
+ original = '../src/vmaware.hpp'
+ mit = '../src/vmaware_MIT.hpp'
+ gpl_string = '/* GPL */'
+ license_string = ' * - License: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)'
+ mit_full_license = ''' * - License: MIT
+ *
+ * MIT License
+ *
+ * Copyright (c) 2024 kernelwernel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+'''
+
+ with open(original, 'r') as file:
+ lines = file.readlines()
+
+ add_string_added = False
+
+ filtered_lines = []
+ for line in lines:
+ if gpl_string in line:
+ # skip
+ continue
+ if license_string in line:
+ filtered_lines.append(mit_full_license)
+ else:
+ filtered_lines.append(line)
+
+ with open(mit, 'w') as file:
+ file.writelines(filtered_lines)
+
+
-def update(filename):
+def update_sections(filename):
with open(filename, 'r') as vmaware_read:
header_content = vmaware_read.readlines()
@@ -120,5 +354,73 @@ def update(filename):
file.writelines(header_content)
-update("../src/vmaware.hpp")
-update("../src/vmaware_MIT.hpp")
\ No newline at end of file
+
+def update_date(filename):
+ # fetch the first arg, which is supposed to be the new version number for a new release
+ args = sys.argv
+ first_arg = args[1] if len(args) > 1 else None
+
+
+ with open(filename, 'r') as file:
+ header_content = file.readlines()
+
+ index = 0
+ banner_line = " * ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ "
+
+ # fetch the index of where the line should be updated
+ for line in header_content:
+ if (banner_line not in line):
+ index += 1
+ else:
+ break
+
+ # find "X.X", where X is an integral
+ def find_pattern(base_str):
+ pattern = r'\d+\.\d+'
+
+ # Search for the pattern in the text
+ match = re.search(pattern, base_str)
+
+ # find match
+ if match:
+ return match.group()
+ print("match found")
+ else:
+ print(f"Version number not found for {base_str}, aborting")
+ sys.exit(1)
+
+
+ # fetch the new version
+ header_version = find_pattern(header_content[index])
+ if first_arg == None:
+ arg_version = header_version
+ else:
+ arg_version = find_pattern(first_arg)
+
+ new_version = ""
+ new_date = ""
+
+ # set the version and date
+ new_version = arg_version
+ new_date = datetime.now().strftime("%B") + " " + str(datetime.now().year)
+
+ # this will be the new content
+ new_content = banner_line + new_version + " (" + new_date + ")"
+
+ if 0 < index <= len(header_content):
+ header_content[index] = new_content + '\n'
+ else:
+ print(f"Line number {line_number} is out of range.")
+ sys.exit(1)
+
+ with open(filename, 'w') as file:
+ file.writelines(header_content)
+
+
+
+arg_check()
+update_MIT()
+update_sections("../src/vmaware.hpp")
+update_sections("../src/vmaware_MIT.hpp")
+update_date("../src/vmaware.hpp")
+update_date("../src/vmaware_MIT.hpp")
\ No newline at end of file
diff --git a/docs/documentation.md b/docs/documentation.md
index 50586c4..9ad869f 100644
--- a/docs/documentation.md
+++ b/docs/documentation.md
@@ -266,10 +266,6 @@ int main() {
}
```
-
-
-
-
## `VM::add_custom()`
@@ -315,6 +311,74 @@ VM::add_custom(50, new_technique);
+## `VM::type()`
+This will return the VM type as a `std::string` based on the brand found. The possible return values can be:
+- `Hypervisor (type 1)`
+- `Hypervisor (type 2)`
+- `Sandbox`
+- `Emulator`
+- `Emulator/Hypervisor (type 2)`
+- `Partitioning Hypervisor`
+- `Container`
+- `Hypervisor (either type 1 or 2)`
+- `Hypervisor (unknown type)`
+- `Compatibility layer`
+- `Paravirtualised/Hypervisor (type 2)`
+- `Hybrid Hyper-V (type 1 and 2)`
+- `Binary Translation Layer/Emulator`
+- `Unknown`
+
+
+
+## `VM::conclusion()`
+This will return the "conclusion" message of what the overall result is as a `std::string`. The `[brand]` part might contain a brand or may as well be empty, depending on whether a brand has been found.
+- `Running in baremetal`
+- `Very unlikely a [brand] VM`
+- `Unlikely a [brand] VM`
+- `Potentially a [brand] VM`
+- `Might be a [brand] VM`
+- `Likely a [brand] VM`
+- `Very likely a [brand] VM`
+- `Running inside a [brand] VM`
+
+
+
+# vmaware struct
+If you prefer having an object to store all the relevant information about the program's environment, you can use the `VM::vmaware` struct:
+
+```cpp
+struct vmaware {
+ bool is_vm;
+ std::uint8_t percentage;
+ std::uint8_t detected_count;
+ std::uint8_t technique_count;
+ std::string brand;
+ std::string type;
+ std::string conclusion;
+};
+```
+
+example:
+```cpp
+#include "vmaware.hpp"
+#include
+
+int main() {
+ VM::vmaware vm;
+
+ std::cout << "Is this a VM? = " << vm.is_vm << "\n";
+ std::cout << "How many techniques detected a VM? = " << vm.detected_count << "%\n";
+ std::cout << "What's the overview in a human-readable message?" << vm.conclusion << "\n";
+}
+```
+
+> [!NOTE]
+> the flag system is compatible for the struct constructor.
+
+
+
+
+
# Flag table
VMAware provides a convenient way to not only check for VMs, but also have the flexibility and freedom for the end-user to choose what techniques are used with complete control over what gets executed or not. This is handled with a flag system.
@@ -405,7 +469,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::UPTIME` | Check if uptime is less than or equal to 2 minutes | | 10% | | | | Spoofable | |
| `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 | | 85% | | | | | |
+| `VM::INTEL_THREAD_MISMATCH` | Check for Intel CPU thread count database if it matches the system's thread count | | 60% | | | | | |
| `VM::XEON_THREAD_MISMATCH` | Same as above, but for Xeon Intel CPUs | | 85% | | | | | |
| `VM::NETTITUDE_VM_MEMORY` | Check for memory regions to detect VM-specific brands | Windows | 75% | | | | | |
| `VM::CPUID_BITSET` | Check for CPUID technique by checking whether all the bits equate to more than 4000 | | 20% | | | | | |
@@ -435,6 +499,8 @@ VMAware provides a convenient way to not only check for VMs, but also have the f
| `VM::SMBIOS_VM_BIT` | Check for the VM bit in the SMBIOS data | Linux | 50% | | | | | |
| `VM::PODMAN_FILE` | Check for podman file in /run/ | Linux | 15% | | | | Spoofable | |
| `VM::WSL_PROC` | Check for WSL or microsoft indications in /proc/ subdirectories | Linux | 30% | | | | | |
+| `VM::ANYRUN_DRIVER` | Check for any.run driver presence | Windows | 65% | | | | | |
+| `VM::ANYRUN_DIRECTORY` | Check for any.run directory and handle the status code | Windows | 35% | | | | | |
diff --git a/src/cli.cpp b/src/cli.cpp
index 1ab2ee3..2f40036 100644
--- a/src/cli.cpp
+++ b/src/cli.cpp
@@ -55,7 +55,10 @@ constexpr const char* red_orange = "\x1B[38;2;247;127;40m";
constexpr const char* green_orange = "\x1B[38;2;174;197;59m";
constexpr const char* grey = "\x1B[38;2;108;108;108m";
-enum arg_enum : std::uint8_t {
+using u8 = std::uint8_t;
+using u32 = std::uint32_t;
+
+enum arg_enum : u8 {
HELP,
VERSION,
ALL,
@@ -73,7 +76,7 @@ enum arg_enum : std::uint8_t {
};
std::bitset<14> arg_bitset;
-const std::uint8_t max_bits = static_cast(VM::MULTIPLE) + 1;
+const u8 max_bits = static_cast(VM::MULTIPLE) + 1;
#if (MSVC)
class win_ansi_enabler_t
@@ -108,9 +111,6 @@ class win_ansi_enabler_t
};
#endif
-// for the technique counts
-std::uint8_t detected_count = 0;
-
[[noreturn]] void help(void) {
std::cout <<
@@ -124,10 +124,10 @@ R"(Usage:
-a | --all run the result with ALL the techniques enabled (might contain false positives)
-d | --detect returns the result as a boolean (1 = VM, 0 = baremetal)
-s | --stdout returns either 0 or 1 to STDOUT without any text output (0 = VM, 1 = baremetal)
- -b | --brand returns the VM brand string (consult documentation for full output list)
+ -b | --brand returns the VM brand string
+ -l | --brand-list returns all the possible VM brand string values
-p | --percent returns the VM percentage between 0 and 100
-c | --conclusion returns the conclusion message string
- -l | --brand-list returns all the possible VM brand string values
-n | --number returns the number of VM detection techniques it performs
-t | --type returns the VM type (if a VM was found)
@@ -150,7 +150,7 @@ R"(Usage:
std::exit(0);
}
-const char* color(const std::uint8_t score) {
+const char* color(const u8 score) {
if (score == 0) { return red; }
else if (score <= 12) { return red; }
else if (score <= 25) { return red_orange; }
@@ -163,36 +163,6 @@ const char* color(const std::uint8_t score) {
return "";
}
-std::string message(const std::uint8_t score, const std::string &brand) {
- constexpr const char* baremetal = "Running in baremetal";
- constexpr const char* very_unlikely = "Very unlikely a VM";
- constexpr const char* unlikely = "Unlikely a VM";
-
- std::string potentially = "Potentially a VM";
- std::string might = "Might be a VM";
- std::string likely = "Likely a VM";
- std::string very_likely = "Very likely a VM";
- std::string inside_vm = "Running inside a VM";
-
- if (brand != "Unknown") {
- potentially = "Potentially a " + brand + " VM";
- might = "Might be a " + brand + " VM";
- likely = "Likely a " + brand + " VM";
- very_likely = "Very likely a " + brand + " VM";
- inside_vm = "Running inside a " + brand + " VM";
- }
-
- if (score == 0) { return baremetal; }
- else if (score <= 20) { return very_unlikely; }
- else if (score <= 35) { return unlikely; }
- else if (score < 50) { return potentially; }
- else if (score <= 62) { return might; }
- else if (score <= 75) { return likely; }
- else if (score < 100) { return very_likely; }
- else if (score == 100) { return inside_vm; }
-
- return "Unknown error";
-}
[[noreturn]] void brand_list() {
std::cout <<
@@ -257,87 +227,6 @@ ANY.RUN
std::exit(0);
}
-std::string type(const std::string &brand_str) {
- if (brand_str.find(" or ") != std::string::npos) {
- return "Unknown";
- }
-
- const std::map type_table {
- // type 1
- { "Xen HVM", "Hypervisor (type 1)" },
- { "VMware ESX", "Hypervisor (type 1)" },
- { "ACRN", "Hypervisor (type 1)" },
- { "QNX hypervisor", "Hypervisor (type 1)" },
- { "Microsoft Hyper-V", "Hypervisor (type 1)" },
- { "Microsoft Azure Hyper-V", "Hypervisor (type 1)" },
- { "Xbox NanoVisor (Hyper-V)", "Hypervisor (type 1)" },
- { "KVM ", "Hypervisor (type 1)" },
- { "bhyve", "Hypervisor (type 1)" },
- { "KVM Hyper-V Enlightenment", "Hypervisor (type 1)" },
- { "QEMU+KVM Hyper-V Enlightenment", "Hypervisor (type 1)" },
- { "QEMU+KVM", "Hypervisor (type 1)" },
- { "Intel HAXM", "Hypervisor (type 1)" },
- { "Intel KGT (Trusty)", "Hypervisor (type 1)" },
- { "SimpleVisor", "Hypervisor (type 1)" },
- { "Google Compute Engine (KVM)", "Hypervisor (type 1)" },
- { "OpenStack (KVM)", "Hypervisor (type 1)" },
- { "KubeVirt (KVM)", "Hypervisor (type 1)" },
- { "IBM PowerVM", "Hypervisor (type 1)" },
- { "AWS Nitro System EC2 (KVM-based)", "Hypervisor (type 1)" },
-
- // type 2
- { "VirtualBox", "Hypervisor (type 2)" },
- { "VMware", "Hypervisor (type 2)" },
- { "VMware Express", "Hypervisor (type 2)" },
- { "VMware GSX", "Hypervisor (type 2)" },
- { "VMware Workstation", "Hypervisor (type 2)" },
- { "VMware Fusion", "Hypervisor (type 2)" },
- { "Parallels", "Hypervisor (type 2)" },
- { "Virtual PC", "Hypervisor (type 2)" },
- { "NetBSD NVMM", "Hypervisor (type 2)" },
- { "OpenBSD VMM", "Hypervisor (type 2)" },
- { "User-mode Linux", "Hypervisor (type 2)" },
-
- // sandbox
- { "Cuckoo", "Sandbox" },
- { "Sandboxie", "Sandbox" },
- { "Hybrid Analysis", "Sandbox" },
- { "CWSandbox", "Sandbox" },
- { "JoeBox", "Sandbox" },
- { "Anubis", "Sandbox" },
- { "Comodo", "Sandbox" },
- { "ThreatExpert", "Sandbox" },
- { "ANY.RUN", "Sandbox"},
-
- // misc
- { "Bochs", "Emulator" },
- { "BlueStacks", "Emulator" },
- { "Microsoft x86-to-ARM", "Emulator" },
- { "QEMU", "Emulator" },
- { "Jailhouse", "Partitioning Hypervisor" },
- { "Unisys s-Par", "Partitioning Hypervisor" },
- { "Docker", "Container" },
- { "Podman", "Container" },
- { "OpenVZ", "Container" },
- { "Microsoft Virtual PC/Hyper-V", "Hypervisor (either type 1 or 2)" },
- { "Lockheed Martin LMHS", "Hypervisor (unknown type)" },
- { "Wine", "Compatibility layer" },
- { "Apple VZ", "Unknown" },
- { "Hyper-V artifact (not an actual VM)", "No VM" },
- { "User-mode Linux", "Paravirtualised" },
- { "WSL", "Hybrid Hyper-V (type 1 and 2)" }, // debatable tbh
- { "Apple Rosetta 2", "Binary Translation Layer/Emulator" },
- };
-
- auto it = type_table.find(brand_str);
-
- if (it != type_table.end()) {
- return it->second;
- }
-
- return "Unknown";
-}
-
bool is_spoofable(const VM::enum_flags flag) {
if (arg_bitset.test(ALL)) {
return false;
@@ -460,7 +349,8 @@ std::bitset settings() {
void general() {
const std::string detected = ("[ " + std::string(green) + "DETECTED" + std::string(ansi_exit) + " ]");
const std::string not_detected = ("[" + std::string(red) + "NOT DETECTED" + std::string(ansi_exit) + "]");
- const std::string spoofable = ("[" + std::string(red) + " SPOOFABLE " + std::string(ansi_exit) + "]");
+ //const std::string spoofable = ("[" + std::string(red) + " SPOOFABLE " + std::string(ansi_exit) + "]");
+ const std::string spoofable = ("[" + std::string(red) + " EASY SPOOF " + std::string(ansi_exit) + "]");
const std::string note = ("[ NOTE ]");
const std::string no_perms = ("[" + std::string(grey) + " NO PERMS " + std::string(ansi_exit) + "]");
const std::string disabled = ("[" + std::string(grey) + " DISABLED " + std::string(ansi_exit) + "]");
@@ -469,7 +359,7 @@ void general() {
auto checker = [&](const VM::enum_flags flag, const char* message) -> void {
if (is_spoofable(flag)) {
if (!arg_bitset.test(SPOOFABLE)) {
- std::cout << spoofable << " Skipped " << message << "\n";
+ std::cout << spoofable << " Skipped " << message << "\n";
return;
}
}
@@ -488,7 +378,6 @@ void general() {
if (VM::check(flag)) {
std::cout << detected << " Checking " << message << "...\n";
- detected_count++;
} else {
std::cout << not_detected << " Checking " << message << "...\n";
}
@@ -631,90 +520,104 @@ void general() {
std::cout << "[DEBUG] theoretical maximum points: " << VM::total_points << "\n";
#endif
- std::string brand = VM::brand(VM::MULTIPLE, settings());
+ // struct containing the whole overview of the VM data
+ VM::vmaware vm(VM::MULTIPLE, settings());
- std::cout << "VM brand: " << ((brand == "Unknown") || (brand == "Hyper-V artifact (not an actual VM)") ? red : green) << brand << ansi_exit << "\n";
- // meaning "if there's no brand conflicts"
- if (brand.find(" or ") == std::string::npos) {
- const std::string type_value = type(brand);
+ // brand manager
+ {
+ std::cout << "VM brand: " << ((vm.brand == "Unknown") || (vm.brand == "Hyper-V artifact (not an actual VM)") ? red : green) << vm.brand << ansi_exit << "\n";
+ }
- std::cout << "VM type: ";
- std::string color = "";
-
- if (type_value == "Unknown" || type_value == "No VM") {
- color = red;
- } else {
- color = green;
- }
+ // type manager
+ {
+ if (vm.brand.find(" or ") == std::string::npos) { // meaning "if there's no brand conflicts"
+ std::cout << "VM type: ";
- std::cout << color << type_value << ansi_exit << "\n";
- }
+ std::string color = "";
- const char* percent_color = "";
- const std::uint8_t percent = VM::percentage(settings());
+ if (vm.type == "Unknown") {
+ color = red;
+ } else {
+ color = green;
+ }
- if (percent == 0) { percent_color = red; }
- else if (percent < 25) { percent_color = red_orange; }
- else if (percent < 50) { percent_color = orange; }
- else if (percent < 75) { percent_color = green_orange; }
- else { percent_color = green; }
+ std::cout << color << vm.type << ansi_exit << "\n";
+ }
+ }
- std::cout << "VM likeliness: " << percent_color << static_cast(percent) << "%" << ansi_exit << "\n";
- const bool is_detected = VM::detect(settings());
+ // percentage manager
+ {
+ const char* percent_color = "";
- std::cout << "VM confirmation: " << (is_detected ? green : red) << std::boolalpha << is_detected << std::noboolalpha << ansi_exit << "\n";
+ if (vm.percentage == 0) { percent_color = red; }
+ else if (vm.percentage < 25) { percent_color = red_orange; }
+ else if (vm.percentage < 50) { percent_color = orange; }
+ else if (vm.percentage < 75) { percent_color = green_orange; }
+ else { percent_color = green; }
+
+ std::cout << "VM likeliness: " << percent_color << static_cast(vm.percentage) << "%" << ansi_exit << "\n";
+ }
- const char* count_color = "";
- switch (detected_count) {
- case 0: count_color = red; break;
- case 1: count_color = red_orange; break;
- case 2: count_color = orange; break;
- case 3: count_color = orange; break;
- case 4: count_color = green_orange; break;
- default:
- // anything over 4 is green
- count_color = green;
+ // VM confirmation manager
+ {
+ std::cout << "VM confirmation: " << (vm.is_vm ? green : red) << std::boolalpha << vm.is_vm << std::noboolalpha << ansi_exit << "\n";
}
- std::cout <<
- "VM detections: " <<
- count_color <<
- static_cast(detected_count) <<
- "/" <<
- static_cast(VM::technique_count) <<
- ansi_exit <<
- "\n\n";
-#if (MSVC)
- using brand_score_t = std::int32_t;
-#else
- using brand_score_t = std::uint8_t;
-#endif
+ // detection count manager
+ {
+ const char* count_color = "";
+
+ switch (vm.detected_count) {
+ case 0: count_color = red; break;
+ case 1: count_color = red_orange; break;
+ case 2: count_color = orange; break;
+ case 3: count_color = orange; break;
+ case 4: count_color = green_orange; break;
+ default:
+ // anything over 4 is green
+ count_color = green;
+ }
+
+ std::cout <<
+ "VM detections: " <<
+ count_color <<
+ static_cast(vm.detected_count) <<
+ "/" <<
+ static_cast(vm.technique_count) <<
+ ansi_exit <<
+ "\n\n";
+ }
- std::map brand_map = VM::brand_map();
- const char* conclusion_color = color(percent);
- std::string conclusion_message = message(percent, brand);
+ // conclusion manager
+ {
+ const char* conclusion_color = color(vm.percentage);
+
+ std::cout
+ << bold
+ << "====== CONCLUSION: "
+ << ansi_exit
+ << conclusion_color << vm.conclusion << " " << ansi_exit
+ << bold
+ << "======"
+ << ansi_exit
+ << "\n\n";
+ }
+
- std::cout
- << bold
- << "====== CONCLUSION: "
- << ansi_exit
- << conclusion_color << conclusion_message << " " << ansi_exit
- << bold
- << "======"
- << ansi_exit
- << "\n\n";
+ // finishing touches with notes
+ if (notes_enabled) {
+ if ((vm.brand == "Hyper-V artifact (not an actual VM)")) {
+ std::cout << note << " The result means that the CLI has found Hyper-V, but as an artifact instead of an actual VM. This means that although the hardware values in fact match with Hyper-V due to how it's designed by Microsoft, the CLI has determined you are NOT in a Hyper-V VM.\n\n";
+ }
- if ((brand == "Hyper-V artifact (not an actual VM)") && notes_enabled) {
- std::cout << note << " The result means that the CLI has found Hyper-V, but as an artifact instead of an actual VM. This means that although the hardware values in fact match with Hyper-V due to how it's designed by Microsoft, the CLI has determined you are NOT in a Hyper-V VM.\n\n";
- } else if (notes_enabled) {
if (!arg_bitset.test(SPOOFABLE)) {
- std::cout << tip << "To enable spoofable techniques, run with the \"--spoofable\" argument\n\n";
+ std::cout << tip << "To enable easily spoofable techniques, run with the \"--spoofable\" argument\n\n";
} else {
std::cout << note << " If you found a false positive, please make sure to create an issue at https://github.com/kernelwernel/VMAware/issues\n\n";
}
@@ -728,7 +631,7 @@ int main(int argc, char* argv[]) {
#endif
const std::vector args(argv + 1, argv + argc); // easier this way
- const std::uint32_t arg_count = argc - 1;
+ const u32 arg_count = argc - 1;
if (arg_count == 0) {
general();
@@ -797,19 +700,19 @@ int main(int argc, char* argv[]) {
}
if (arg_bitset.test(NUMBER)) {
- std::cout << static_cast(VM::technique_count) << "\n";
+ std::cout << static_cast(VM::technique_count) << "\n";
return 0;
}
// critical returners
- const std::uint32_t returners = (
- static_cast(arg_bitset.test(STDOUT)) +
- static_cast(arg_bitset.test(PERCENT)) +
- static_cast(arg_bitset.test(DETECT)) +
- static_cast(arg_bitset.test(BRAND)) +
- static_cast(arg_bitset.test(TYPE)) +
- static_cast(arg_bitset.test(CONCLUSION))
+ const u32 returners = (
+ static_cast(arg_bitset.test(STDOUT)) +
+ static_cast(arg_bitset.test(PERCENT)) +
+ static_cast(arg_bitset.test(DETECT)) +
+ static_cast(arg_bitset.test(BRAND)) +
+ static_cast(arg_bitset.test(TYPE)) +
+ static_cast(arg_bitset.test(CONCLUSION))
);
if (returners > 0) { // at least one of the options are set
@@ -823,7 +726,7 @@ int main(int argc, char* argv[]) {
}
if (arg_bitset.test(PERCENT)) {
- std::cout << static_cast(VM::percentage(VM::NO_MEMO, settings())) << "\n";
+ std::cout << static_cast(VM::percentage(VM::NO_MEMO, settings())) << "\n";
return 0;
}
@@ -838,18 +741,12 @@ int main(int argc, char* argv[]) {
}
if (arg_bitset.test(TYPE)) {
- const std::string brand = VM::brand(VM::NO_MEMO, VM::MULTIPLE, settings());
- std::cout << type(brand) << "\n";
+ std::cout << VM::type(VM::NO_MEMO, VM::MULTIPLE, settings()) << "\n";
return 0;
}
if (arg_bitset.test(CONCLUSION)) {
- std::uint8_t percent = 0;
-
- percent = VM::percentage(VM::NO_MEMO, settings());
-
- const std::string brand = VM::brand(VM::MULTIPLE, settings());
- std::cout << message(percent, brand) << "\n";
+ std::cout << VM::conclusion(VM::NO_MEMO, VM::MULTIPLE, settings()) << "\n";
return 0;
}
}
diff --git a/src/vmaware.hpp b/src/vmaware.hpp
index 2dd01f6..fc37c98 100644
--- a/src/vmaware.hpp
+++ b/src/vmaware.hpp
@@ -23,14 +23,14 @@
*
*
* ================================ SECTIONS ==================================
- * - enums for publicly accessible techniques => line 322
- * - struct for internal cpu operations => line 581
- * - struct for internal memoization => line 1007
- * - struct for internal utility functions => line 1134
- * - struct for internal core components => line 9152
- * - start of internal VM detection techniques => line 2409
- * - start of public VM detection functions => line 9495
- * - start of externally defined variables => line 10095
+ * - enums for publicly accessible techniques => line 324
+ * - struct for internal cpu operations => line 588
+ * - struct for internal memoization => line 1014
+ * - struct for internal utility functions => line 1142
+ * - struct for internal core components => line 9157
+ * - start of internal VM detection techniques => line 2438
+ * - start of public VM detection functions => line 9519
+ * - start of externally defined variables => line 10357
*
*
* ================================ EXAMPLE ==================================
@@ -49,6 +49,8 @@
* }
*/
+#pragma once
+
#if (defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64) || defined(__MINGW32__))
#define MSVC 1
#define LINUX 0
@@ -349,18 +351,18 @@ struct VM {
DISK_SIZE,
VBOX_DEFAULT,
VBOX_NETWORK,
- COMPUTER_NAME, // GPL
- WINE_CHECK, // GPL
- HOSTNAME, // GPL
- MEMORY, // GPL
- VBOX_WINDOW_CLASS, // GPL
- LOADED_DLLS, // GPL
- KVM_REG, // GPL
- KVM_DRIVERS, // GPL
- KVM_DIRS, // GPL
- AUDIO, // GPL
- QEMU_DIR, // GPL
- MOUSE_DEVICE, // GPL
+/* GPL */ COMPUTER_NAME,
+/* GPL */ WINE_CHECK,
+/* GPL */ HOSTNAME,
+/* GPL */ MEMORY,
+/* GPL */ VBOX_WINDOW_CLASS,
+/* GPL */ LOADED_DLLS,
+/* GPL */ KVM_REG,
+/* GPL */ KVM_DRIVERS,
+/* GPL */ KVM_DIRS,
+/* GPL */ AUDIO,
+/* GPL */ QEMU_DIR,
+/* GPL */ MOUSE_DEVICE,
VM_PROCESSES,
LINUX_USER_HOST,
GAMARUE,
@@ -438,7 +440,7 @@ struct VM {
ANYRUN_DRIVER,
ANYRUN_DIRECTORY,
- // start of non-technique flags (THE ORDERING IS VERY SPECIFIC HERE AND MIGHT BREAK SOMETHING IF RE-ORDERED)
+ // start of settings technique flags (THE ORDERING IS VERY SPECIFIC HERE AND MIGHT BREAK SOMETHING IF RE-ORDERED)
NO_MEMO,
HIGH_THRESHOLD,
NULL_ARG, // does nothing, just a placeholder flag mainly for the CLI
@@ -448,7 +450,7 @@ struct VM {
private:
static constexpr u8 enum_size = MULTIPLE; // get enum size through value of last element
- static constexpr u8 non_technique_count = MULTIPLE - NO_MEMO + 1; // get number of non-technique flags like VM::NO_MEMO for example
+ static constexpr u8 non_technique_count = MULTIPLE - NO_MEMO + 1; // get number of settings technique flags like VM::NO_MEMO for example
static constexpr u8 INVALID = 255; // explicit invalid technique macro
static constexpr u16 maximum_points = 4765; // theoretical total points if all VM detections returned true (which is practically impossible)
static constexpr u16 high_threshold_score = 300; // new threshold score from 100 to 350 if VM::HIGH_THRESHOLD flag is enabled
@@ -463,6 +465,11 @@ struct VM {
static constexpr u8 non_technique_begin = NO_MEMO;
static constexpr u8 non_technique_end = enum_end;
+
+ // this is specifically meant for VM::detected_count() to
+ // get the total number of techniques that detected a VM
+ static u8 detected_count_num;
+
public:
static constexpr u8 technique_count = NO_MEMO; // get total number of techniques
static std::vector technique_vector;
@@ -763,6 +770,14 @@ struct VM {
u32 sig_reg[3] = { 0 };
+ if (
+ (sig_reg[0] == 0) &&
+ (sig_reg[1] == 0) &&
+ (sig_reg[2] == 0)
+ ) {
+ return { "", "" };
+ }
+
if (!cpuid_thingy(p_leaf, sig_reg, 1)) {
return { "", "" };
}
@@ -772,6 +787,11 @@ struct VM {
return str;
};
+ // the reason why there's 2 is because depending on the leaf,
+ // the last 4 characters might be switched with the middle
+ // characters for some fuckin reason, idk why this is even a thing
+ // so this function basically returns the same string but with
+ // the 4~8 and 8~12 characters switched for one, and the other isn't.
std::stringstream ss;
std::stringstream ss2;
@@ -1131,6 +1151,7 @@ struct VM {
};
};
+
// miscellaneous functionalities
struct util {
#if (LINUX)
@@ -1163,7 +1184,7 @@ struct VM {
}
std::vector buffer((std::istreambuf_iterator(file)),
- std::istreambuf_iterator());
+ std::istreambuf_iterator());
file.close();
@@ -1213,7 +1234,7 @@ struct VM {
return (
(uid != euid) ||
(euid == 0)
- );
+ );
#elif (MSVC)
BOOL is_admin = FALSE;
HANDLE hToken = NULL;
@@ -1671,7 +1692,16 @@ struct VM {
/**
* @brief Checks whether Hyper-V host artifacts are present instead of an actual Hyper-V VM
- * @note idea and credits to Requiem (https://github.com/NotRequiem)
+ * @note Hyper-V has an obscure feature where if it's enabled in the host system, the CPU
+ * hardware values makes it look like the whole system is running inside Hyper-V,
+ * which isn't true. This makes it a challenge to determine whether the hardware
+ * values the library is collecting is either a real Hyper-V VM, or just the artifacts
+ * of what Hyper-V has left as a consequence of having it enabled in the host system.
+ * The reason why this is a problem is because the library might falsely conclude that
+ * your the host system is running in Hyper-V, which is a false positive. This is where
+ * the Hyper-X mechanism comes into play to distinguish between these two.
+ * @author idea by Requiem (https://github.com/NotRequiem)
+ * @link graph to explain how this works: https://github.com/kernelwernel/VMAware/blob/main/assets/Hyper-X.png
*/
[[nodiscard]] static bool hyper_x() {
#if (!MSVC)
@@ -1682,73 +1712,81 @@ struct VM {
return memo::hyperv::fetch();
}
- auto add = [](const bool result) -> bool {
- memo::hyperv::store(result);
- if (result == true) {
- core::add(HYPERV_ARTIFACT);
- }
-
- return result;
+ auto root_partition = []() -> bool {
+ u32 ebx, unused = 0;
+ cpu::cpuid(unused, ebx, unused, unused, 0x40000003);
+ return (ebx & 1);
};
- char out[sizeof(int32_t) * 4 + 1] = { 0 }; // e*x size + number of e*x registers + null terminator
- cpu::cpuid((int*)out, cpu::leaf::hypervisor);
+ auto eax = []() -> bool {
+ char out[sizeof(int32_t) * 4 + 1] = { 0 }; // e*x size + number of e*x registers + null terminator
+ cpu::cpuid((int*)out, cpu::leaf::hypervisor);
- const u32 eax = static_cast(out[0]);
+ const u32 eax = static_cast(out[0]);
- core_debug("HYPER_X: eax = ", eax);
+ core_debug("HYPER_X: eax = ", eax);
- const bool is_eax_valid = ((eax == 11) || (eax == 12));
+ return ((eax == 11) || (eax == 12));
+ };
- const std::array cpu = cpu::cpu_manufacturer(cpu::leaf::hypervisor);
+ auto cpu_vmid = []() -> bool {
+ const auto cpu = cpu::cpu_manufacturer(cpu::leaf::hypervisor);
- const bool is_cpu_hyperv = (
- (cpu.at(0) == "Microsoft Hv") ||
- (cpu.at(1) == "Microsoft Hv")
- );
-
- if (is_eax_valid || is_cpu_hyperv) {
- // SMBIOS check
- const std::string smbios = SMBIOS_string();
+ return (
+ (cpu.at(0) == "Microsoft Hv") ||
+ (cpu.at(1) == "Microsoft Hv")
+ );
+ };
- core_debug("HYPER_X: SMBIOS string = ", smbios);
+ // must require at least 2 to continue
+ const u8 points = (root_partition() + eax() + cpu_vmid());
- if (smbios == "VIRTUAL MACHINE") {
- return add(false);
- }
+ if (points >= 2) {
+ // SMBIOS check
+ auto is_smbios_hyperv = []() -> bool {
+ const std::string smbios = SMBIOS_string();
+ core_debug("HYPER_X: SMBIOS string = ", smbios);
+ return (smbios == "VIRTUAL MACHINE");
+ };
// motherboard check
- const bool motherboard = motherboard_string(L"Microsoft Corporation");
-
- core_debug("HYPER_X: motherboard string = ", motherboard);
-
- if (motherboard) {
- return add(false);
- }
+ auto is_motherboard_hyperv = []() -> bool {
+ const bool motherboard = motherboard_string(L"Microsoft Corporation");
+ core_debug("HYPER_X: motherboard string match = ", motherboard);
+ return motherboard;
+ };
// event log check (slow, so in last place)
- std::wstring logName = L"Microsoft-Windows-Kernel-PnP/Configuration";
- std::vector searchStrings = { L"Virtual_Machine", L"VMBUS" };
-
- const bool event_log = util::query_event_logs(logName, searchStrings);
+ auto is_event_log_hyperv = []() -> bool {
+ std::wstring logName = L"Microsoft-Windows-Kernel-PnP/Configuration";
+ std::vector searchStrings = { L"Virtual_Machine", L"VMBUS" };
+
+ return (util::query_event_logs(logName, searchStrings));
+ };
- if (event_log) {
- return add(false);
- }
+ // "if it's hyper-v and NOT an artifact"
+ const bool is_hyperv = (
+ is_smbios_hyperv() ||
+ is_motherboard_hyperv() ||
+ is_event_log_hyperv()
+ );
- // at this point, it's fair to assume it's Hyper-V artifacts on
- // host since none of the "VM-only" techniques returned true
- return add(true);
- //} else if () {
- // actual Hyper-V VM, might do something within this scope in the future idk
- //return add(false);
- } else {
- return add(false);
+ memo::hyperv::store(is_hyperv);
+
+ if (is_hyperv) {
+ return true;
+ } else {
+ core::add(HYPERV_ARTIFACT);
+ return false;
+ }
}
+
+ memo::hyperv::store(false);
+ return false;
#endif
}
@@ -3611,485 +3649,461 @@ struct VM {
}
- /**
- * @brief Check if the computer name (not username to be clear) is VM-specific
- * @category Windows
- * @author InviZzzible project
- * @copyright GPL-3.0
- */
- [[nodiscard]] static bool computer_name_match() try {
-#if (!MSVC)
- return false;
-#else
- auto out_length = MAX_PATH;
- std::vector comp_name(static_cast(out_length), 0);
- GetComputerNameA((LPSTR)comp_name.data(), (LPDWORD)&out_length);
-
- auto compare = [&](const std::string& s) -> bool {
- return (std::strcmp((LPCSTR)comp_name.data(), s.c_str()) == 0);
- };
-
- debug("COMPUTER_NAME: fetched = ", (LPCSTR)comp_name.data());
-
- if (compare("InsideTm") || compare("TU-4NH09SMCG1HC")) { // anubis
- debug("COMPUTER_NAME: detected Anubis");
- return core::add(ANUBIS);
- }
-
- if (compare("klone_x64-pc") || compare("tequilaboomboom")) { // general
- debug("COMPUTER_NAME: detected general (VM but unknown)");
- return true;
- }
-
- return false;
-#endif
- }
- catch (...) {
- debug("COMPUTER_NAME: caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check wine_get_unix_file_name file for Wine
- * @author pafish project
- * @link https://github.com/a0rtega/pafish/blob/master/pafish/wine.c
- * @category Windows
- * @copyright GPL-3.0
- */
- [[nodiscard]] static bool wine() try {
-#if (!MSVC)
- return false;
-#else
- HMODULE k32;
- k32 = GetModuleHandle(TEXT("kernel32.dll"));
-
- if (k32 != NULL) {
- if (GetProcAddress(k32, "wine_get_unix_file_name") != NULL) {
- return core::add(WINE);
- }
- }
-
- return false;
-#endif
- }
- catch (...) {
- debug("WINE_CHECK: caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check if hostname is specific
- * @author InviZzzible project
- * @category Windows
- * @copyright GPL-3.0
- */
- [[nodiscard]] static bool hostname_match() try {
-#if (!MSVC)
- return false;
-#else
- auto out_length = MAX_PATH;
- std::vector dns_host_name(static_cast(out_length), 0);
- GetComputerNameExA(ComputerNameDnsHostname, (LPSTR)dns_host_name.data(), (LPDWORD)&out_length);
-
- debug("HOSTNAME: ", (LPCSTR)dns_host_name.data());
-
- return (!lstrcmpiA((LPCSTR)dns_host_name.data(), "SystemIT"));
-#endif
- }
- catch (...) {
- debug("HOSTNAME: caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check if memory space is far too low for a physical machine
- * @author Al-Khaser project
- * @category x86?
- * @copyright GPL-3.0
- */
- [[nodiscard]] static bool low_memory_space() try {
- constexpr u64 min_ram_1gb = (1024LL * (1024LL * (1024LL * 1LL)));
- const u64 ram = util::get_memory_space();
-
- debug("MEMORY: ram size (GB) = ", ram);
- debug("MEMORY: minimum ram size (GB) = ", min_ram_1gb);
-
- return (ram < min_ram_1gb);
- }
- catch (...) {
- debug("MEMORY: caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check for the window class for VirtualBox
- * @category Windows
- * @author Al-Khaser Project
- * @copyright GPL-3.0
- */
- [[nodiscard]] static bool vbox_window_class() try {
-#if (!MSVC)
- return false;
-#else
- HWND hClass = FindWindow(_T("VBoxTrayToolWndClass"), NULL);
- HWND hWindow = FindWindow(NULL, _T("VBoxTrayToolWnd"));
-
- if (hClass || hWindow) {
- return core::add(VBOX);
- }
-
- return false;
-#endif
- }
- catch (...) {
- debug("VBOX_WINDOW_CLASS: caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check for loaded DLLs in the process
- * @category Windows
- * @author LordNoteworthy
- * @note modified code from Al-Khaser project
- * @link https://github.com/LordNoteworthy/al-khaser/blob/c68fbd7ba0ba46315e819b490a2c782b80262fcd/al-khaser/Anti%20VM/Generic.cpp
- */
- [[nodiscard]] static bool loaded_dlls() try {
-#if (!MSVC)
- return false;
-#else
- HMODULE hDll;
-
- constexpr std::array szDlls = {{
- "avghookx.dll", // AVG
- "avghooka.dll", // AVG
- "snxhk.dll", // Avast
- "sbiedll.dll", // Sandboxie
- "dbghelp.dll", // WindBG
- "api_log.dll", // iDefense Lab
- "dir_watch.dll", // iDefense Lab
- "pstorec.dll", // SunBelt CWSandbox
- "vmcheck.dll", // Virtual PC
- "wpespy.dll", // WPE Pro
- "cmdvrt64.dll", // Comodo Container
- "cmdvrt32.dll" // Comodo Container
- }};
-
- for (const auto& key : szDlls) {
- const char* dll = key;
-
- hDll = GetModuleHandleA(dll); // Use GetModuleHandleA for ANSI strings
-
- if (hDll != NULL && dll != NULL) {
- if (strcmp(dll, "sbiedll.dll") == 0) { return core::add(SANDBOXIE); }
- if (strcmp(dll, "pstorec.dll") == 0) { return core::add(CWSANDBOX); }
- if (strcmp(dll, "vmcheck.dll") == 0) { return core::add(VPC); }
- if (strcmp(dll, "cmdvrt32.dll") == 0) { return core::add(COMODO); }
- if (strcmp(dll, "cmdvrt64.dll") == 0) { return core::add(COMODO); }
-
- return true;
- }
- }
-
- return false;
-#endif
- }
- catch (...) {
- debug("LOADED_DLLS:", "caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check for KVM-specific registry strings
- * @category Windows
- * @note idea is from Al-Khaser, slightly modified code
- * @author LordNoteWorthy
- * @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp
- */
- [[nodiscard]] static bool kvm_registry() try {
-#if (!MSVC)
- return false;
-#else
- auto registry_exists = [](const TCHAR* key) -> bool {
- HKEY keyHandle;
-
- if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE, &keyHandle) == ERROR_SUCCESS) {
- RegCloseKey(keyHandle);
- return true;
- }
-
- return false;
- };
-
- constexpr std::array keys = {{
- _T("SYSTEM\\ControlSet001\\Services\\vioscsi"),
- _T("SYSTEM\\ControlSet001\\Services\\viostor"),
- _T("SYSTEM\\ControlSet001\\Services\\VirtIO-FS Service"),
- _T("SYSTEM\\ControlSet001\\Services\\VirtioSerial"),
- _T("SYSTEM\\ControlSet001\\Services\\BALLOON"),
- _T("SYSTEM\\ControlSet001\\Services\\BalloonService"),
- _T("SYSTEM\\ControlSet001\\Services\\netkvm"),
- }};
-
- for (const auto& key : keys) {
- if (registry_exists(key)) {
- return core::add(KVM);
- }
- }
-
- return false;
-#endif
- }
- catch (...) {
- debug("KVM_REG: ", "caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check for KVM-specific .sys files in system driver directory
- * @category Windows
- * @note idea is from Al-Khaser, slightly modified code
- * @author LordNoteWorthy
- * @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp
- */
- [[nodiscard]] static bool kvm_drivers() try {
-#if (!MSVC)
- return false;
-#else
- constexpr std::array keys = { {
- _T("System32\\drivers\\balloon.sys"),
- _T("System32\\drivers\\netkvm.sys"),
- _T("System32\\drivers\\pvpanic.sys"),
- _T("System32\\drivers\\viofs.sys"),
- _T("System32\\drivers\\viogpudo.sys"),
- _T("System32\\drivers\\vioinput.sys"),
- _T("System32\\drivers\\viorng.sys"),
- _T("System32\\drivers\\vioscsi.sys"),
- _T("System32\\drivers\\vioser.sys"),
- _T("System32\\drivers\\viostor.sys")
- } };
-
- TCHAR szWinDir[MAX_PATH] = _T("");
- TCHAR szPath[MAX_PATH] = _T("");
- PVOID OldValue = NULL;
-
- if (GetWindowsDirectory(szWinDir, MAX_PATH) == 0) {
- return false;
- }
-
- if (util::is_wow64()) {
- Wow64DisableWow64FsRedirection(&OldValue);
- }
-
- bool is_vm = false;
-
- for (const auto& key : keys) {
- PathCombine(szPath, szWinDir, key);
- if (util::exists(szPath)) {
- is_vm = true;
- break;
- }
- }
-
- if (util::is_wow64()) {
- Wow64RevertWow64FsRedirection(&OldValue);
- }
-
- return is_vm;
-#endif
- }
- catch (...) {
- debug("KVM_DRIVERS: ", "caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check for KVM directory "Virtio-Win"
- * @category Windows
- * @author LordNoteWorthy
- * @note from Al-Khaser project
- * @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp
- */
- [[nodiscard]] static bool kvm_directories() try {
-#if (!MSVC)
- return false;
-#else
- TCHAR szProgramFile[MAX_PATH];
- TCHAR szPath[MAX_PATH] = _T("");
- TCHAR szTarget[MAX_PATH] = _T("Virtio-Win\\");
-
- if (util::is_wow64()) {
- ExpandEnvironmentStrings(_T("%ProgramW6432%"), szProgramFile, ARRAYSIZE(szProgramFile));
- } else {
- SHGetSpecialFolderPath(NULL, szProgramFile, CSIDL_PROGRAM_FILES, FALSE);
- }
-
- PathCombine(szPath, szProgramFile, szTarget);
- return util::exists(szPath);
-#endif
- }
- catch (...) {
- debug("KVM_DIRS: ", "caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check if audio device is present
- * @category Windows
- * @author CheckPointSW (InviZzzible project)
- * @link https://github.com/CheckPointSW/InviZzzible/blob/master/SandboxEvasion/helper.cpp
- * @copyright GPL-3.0
- */
- [[nodiscard]] static bool check_audio() try {
-#if (!MSVC)
- return false;
-#else
- PCWSTR wszfilterName = L"audio_device_random_name";
-
- if (FAILED(CoInitialize(NULL)))
- return false;
-
- IGraphBuilder* pGraph = nullptr;
- if (FAILED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&pGraph)))
- return false;
-
- // First anti-emulation check: If AddFilter is called with NULL as a first argument it should return the E_POINTER error code.
- // Some emulators may implement unknown COM interfaces in a generic way, so they will probably fail here.
- if (E_POINTER != pGraph->AddFilter(NULL, wszfilterName))
- return true;
-
- // Initializes a simple Audio Renderer, error code is not checked,
- // but pBaseFilter will be set to NULL upon failure and the code will eventually fail later.
- IBaseFilter* pBaseFilter = nullptr;
-
- HRESULT hr = CoCreateInstance(CLSID_AudioRender, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pBaseFilter);
- if (FAILED(hr)) {
- return false;
- }
-
- // Adds the previously created Audio Renderer to the Filter Graph, no error checks
- pGraph->AddFilter(pBaseFilter, wszfilterName);
-
- // Tries to find the filter that was just added; in case of any previously not checked error (or wrong emulation)
- // this function won't find the filter and the sandbox/emulator will be successfully detected.
- IBaseFilter* pBaseFilter2 = nullptr;
- pGraph->FindFilterByName(wszfilterName, &pBaseFilter2);
- if (nullptr == pBaseFilter2)
- return true;
-
- // Checks if info.achName is equal to the previously added filterName, if not - poor API emulation
- FILTER_INFO info = { 0 };
- pBaseFilter2->QueryFilterInfo(&info);
- if (0 != wcscmp(info.achName, wszfilterName))
- return false;
-
- // Checks if the API sets a proper IReferenceClock pointer
- IReferenceClock* pClock = nullptr;
- if (0 != pBaseFilter2->GetSyncSource(&pClock))
- return false;
- if (0 != pClock)
- return false;
-
- // Checks if CLSID is different from 0
- CLSID clsID = { 0 };
- pBaseFilter2->GetClassID(&clsID);
- if (clsID.Data1 == 0)
- return true;
-
- if (nullptr == pBaseFilter2)
- return true;
-
- // Just checks if the call was successful
- IEnumPins* pEnum = nullptr;
- if (0 != pBaseFilter2->EnumPins(&pEnum))
- return true;
-
- // The reference count returned by AddRef has to be higher than 0
- if (0 == pBaseFilter2->AddRef())
- return true;
-
- return false;
-#endif
- }
- catch (...) {
- debug("AUDIO: ", "caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check for QEMU-specific blacklisted directories
- * @author LordNoteworthy
- * @link https://github.com/LordNoteworthy/al-khaser/blob/master/al-khaser/AntiVM/Qemu.cpp
- * @category Windows
- * @note from al-khaser project
- * @copyright GPL-3.0
- */
- [[nodiscard]] static bool qemu_dir() try {
-#if (!MSVC)
- return false;
-#else
- TCHAR szProgramFile[MAX_PATH];
- TCHAR szPath[MAX_PATH] = _T("");
-
- const TCHAR* szDirectories[] = {
- _T("qemu-ga"), // QEMU guest agent.
- _T("SPICE Guest Tools"), // SPICE guest tools.
- };
-
- WORD iLength = sizeof(szDirectories) / sizeof(szDirectories[0]);
- for (int i = 0; i < iLength; i++) {
- TCHAR msg[256] = _T("");
-
- if (util::is_wow64())
- ExpandEnvironmentStrings(_T("%ProgramW6432%"), szProgramFile, ARRAYSIZE(szProgramFile));
- else
- SHGetSpecialFolderPath(NULL, szProgramFile, CSIDL_PROGRAM_FILES, FALSE);
-
- PathCombine(szPath, szProgramFile, szDirectories[i]);
-
- if (util::exists(szPath))
- return core::add(QEMU);
- }
-
- return false;
-#endif
- }
- catch (...) {
- debug("QEMU_DIR: ", "caught error, returned false");
- return false;
- }
-
-
- /**
- * @brief Check for the presence of a mouse device
- * @category Windows
- * @author a0rtega
- * @link https://github.com/a0rtega/pafish/blob/master/pafish/rtt.c
- * @note from pafish project
- * @copyright GPL
- */
- [[nodiscard]] static bool mouse_device() try {
-#if (!MSVC)
- return false;
-#else
- int res;
- res = GetSystemMetrics(SM_MOUSEPRESENT);
- return (res == 0);
-#endif
- }
- catch (...) {
- debug("MOUSE_DEVICE: caught error, returned false");
- return false;
- }
+/* GPL */ // @brief Check if the computer name (not username to be clear) is VM-specific
+/* GPL */ // @category Windows
+/* GPL */ // @author InviZzzible project
+/* GPL */ // @copyright GPL-3.0
+/* GPL */ [[nodiscard]] static bool computer_name_match() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ auto out_length = MAX_PATH;
+/* GPL */ std::vector comp_name(static_cast(out_length), 0);
+/* GPL */ GetComputerNameA((LPSTR)comp_name.data(), (LPDWORD)&out_length);
+/* GPL */
+/* GPL */ auto compare = [&](const std::string& s) -> bool {
+/* GPL */ return (std::strcmp((LPCSTR)comp_name.data(), s.c_str()) == 0);
+/* GPL */ };
+/* GPL */
+/* GPL */ debug("COMPUTER_NAME: fetched = ", (LPCSTR)comp_name.data());
+/* GPL */
+/* GPL */ if (compare("InsideTm") || compare("TU-4NH09SMCG1HC")) { // anubis
+/* GPL */ debug("COMPUTER_NAME: detected Anubis");
+/* GPL */ return core::add(ANUBIS);
+/* GPL */ }
+/* GPL */
+/* GPL */ if (compare("klone_x64-pc") || compare("tequilaboomboom")) { // general
+/* GPL */ debug("COMPUTER_NAME: detected general (VM but unknown)");
+/* GPL */ return true;
+/* GPL */ }
+/* GPL */
+/* GPL */ return false;
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("COMPUTER_NAME: caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check wine_get_unix_file_name file for Wine
+/* GPL */ // @author pafish project
+/* GPL */ // @link https://github.com/a0rtega/pafish/blob/master/pafish/wine.c
+/* GPL */ // @category Windows
+/* GPL */ // @copyright GPL-3.0
+/* GPL */ [[nodiscard]] static bool wine() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ HMODULE k32;
+/* GPL */ k32 = GetModuleHandle(TEXT("kernel32.dll"));
+/* GPL */
+/* GPL */ if (k32 != NULL) {
+/* GPL */ if (GetProcAddress(k32, "wine_get_unix_file_name") != NULL) {
+/* GPL */ return core::add(WINE);
+/* GPL */ }
+/* GPL */ }
+/* GPL */
+/* GPL */ return false;
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("WINE_CHECK: caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check if hostname is specific
+/* GPL */ // @author InviZzzible project
+/* GPL */ // @category Windows
+/* GPL */ // @copyright GPL-3.0
+/* GPL */ [[nodiscard]] static bool hostname_match() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ auto out_length = MAX_PATH;
+/* GPL */ std::vector dns_host_name(static_cast(out_length), 0);
+/* GPL */ GetComputerNameExA(ComputerNameDnsHostname, (LPSTR)dns_host_name.data(), (LPDWORD)&out_length);
+/* GPL */
+/* GPL */ debug("HOSTNAME: ", (LPCSTR)dns_host_name.data());
+/* GPL */
+/* GPL */ return (!lstrcmpiA((LPCSTR)dns_host_name.data(), "SystemIT"));
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("HOSTNAME: caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check if memory space is far too low for a physical machine
+/* GPL */ // @author Al-Khaser project
+/* GPL */ // @category x86?
+/* GPL */ // @copyright GPL-3.0
+/* GPL */ [[nodiscard]] static bool low_memory_space() try {
+/* GPL */ constexpr u64 min_ram_1gb = (1024LL * (1024LL * (1024LL * 1LL)));
+/* GPL */ const u64 ram = util::get_memory_space();
+/* GPL */
+/* GPL */ debug("MEMORY: ram size (GB) = ", ram);
+/* GPL */ debug("MEMORY: minimum ram size (GB) = ", min_ram_1gb);
+/* GPL */
+/* GPL */ return (ram < min_ram_1gb);
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("MEMORY: caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check for the window class for VirtualBox
+/* GPL */ // @category Windows
+/* GPL */ // @author Al-Khaser Project
+/* GPL */ // @copyright GPL-3.0
+/* GPL */ [[nodiscard]] static bool vbox_window_class() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ HWND hClass = FindWindow(_T("VBoxTrayToolWndClass"), NULL);
+/* GPL */ HWND hWindow = FindWindow(NULL, _T("VBoxTrayToolWnd"));
+/* GPL */
+/* GPL */ if (hClass || hWindow) {
+/* GPL */ return core::add(VBOX);
+/* GPL */ }
+/* GPL */
+/* GPL */ return false;
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("VBOX_WINDOW_CLASS: caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check for loaded DLLs in the process
+/* GPL */ // @category Windows
+/* GPL */ // @author LordNoteworthy
+/* GPL */ // @note modified code from Al-Khaser project
+/* GPL */ // @link https://github.com/LordNoteworthy/al-khaser/blob/c68fbd7ba0ba46315e819b490a2c782b80262fcd/al-khaser/Anti%20VM/Generic.cpp
+/* GPL */ [[nodiscard]] static bool loaded_dlls() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ HMODULE hDll;
+/* GPL */
+/* GPL */ constexpr std::array szDlls = {{
+/* GPL */ "avghookx.dll", // AVG
+/* GPL */ "avghooka.dll", // AVG
+/* GPL */ "snxhk.dll", // Avast
+/* GPL */ "sbiedll.dll", // Sandboxie
+/* GPL */ "dbghelp.dll", // WindBG
+/* GPL */ "api_log.dll", // iDefense Lab
+/* GPL */ "dir_watch.dll", // iDefense Lab
+/* GPL */ "pstorec.dll", // SunBelt CWSandbox
+/* GPL */ "vmcheck.dll", // Virtual PC
+/* GPL */ "wpespy.dll", // WPE Pro
+/* GPL */ "cmdvrt64.dll", // Comodo Container
+/* GPL */ "cmdvrt32.dll" // Comodo Container
+/* GPL */ }};
+/* GPL */
+/* GPL */ for (const auto& key : szDlls) {
+/* GPL */ const char* dll = key;
+/* GPL */
+/* GPL */ hDll = GetModuleHandleA(dll); // Use GetModuleHandleA for ANSI strings
+/* GPL */
+/* GPL */ if (hDll != NULL && dll != NULL) {
+/* GPL */ if (strcmp(dll, "sbiedll.dll") == 0) { return core::add(SANDBOXIE); }
+/* GPL */ if (strcmp(dll, "pstorec.dll") == 0) { return core::add(CWSANDBOX); }
+/* GPL */ if (strcmp(dll, "vmcheck.dll") == 0) { return core::add(VPC); }
+/* GPL */ if (strcmp(dll, "cmdvrt32.dll") == 0) { return core::add(COMODO); }
+/* GPL */ if (strcmp(dll, "cmdvrt64.dll") == 0) { return core::add(COMODO); }
+/* GPL */
+/* GPL */ return true;
+/* GPL */ }
+/* GPL */ }
+/* GPL */
+/* GPL */ return false;
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("LOADED_DLLS:", "caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check for KVM-specific registry strings
+/* GPL */ // @category Windows
+/* GPL */ // @note idea is from Al-Khaser, slightly modified code
+/* GPL */ // @author LordNoteWorthy
+/* GPL */ // @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp
+/* GPL */ [[nodiscard]] static bool kvm_registry() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ auto registry_exists = [](const TCHAR* key) -> bool {
+/* GPL */ HKEY keyHandle;
+/* GPL */
+/* GPL */ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE, &keyHandle) == ERROR_SUCCESS) {
+/* GPL */ RegCloseKey(keyHandle);
+/* GPL */ return true;
+/* GPL */ }
+/* GPL */
+/* GPL */ return false;
+/* GPL */ };
+/* GPL */
+/* GPL */ constexpr std::array keys = {{
+/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\vioscsi"),
+/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\viostor"),
+/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\VirtIO-FS Service"),
+/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\VirtioSerial"),
+/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\BALLOON"),
+/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\BalloonService"),
+/* GPL */ _T("SYSTEM\\ControlSet001\\Services\\netkvm"),
+/* GPL */ }};
+/* GPL */
+/* GPL */ for (const auto& key : keys) {
+/* GPL */ if (registry_exists(key)) {
+/* GPL */ return core::add(KVM);
+/* GPL */ }
+/* GPL */ }
+/* GPL */
+/* GPL */ return false;
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("KVM_REG: ", "caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check for KVM-specific .sys files in system driver directory
+/* GPL */ // @category Windows
+/* GPL */ // @note idea is from Al-Khaser, slightly modified code
+/* GPL */ // @author LordNoteWorthy
+/* GPL */ // @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp
+/* GPL */ [[nodiscard]] static bool kvm_drivers() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ constexpr std::array keys = { {
+/* GPL */ _T("System32\\drivers\\balloon.sys"),
+/* GPL */ _T("System32\\drivers\\netkvm.sys"),
+/* GPL */ _T("System32\\drivers\\pvpanic.sys"),
+/* GPL */ _T("System32\\drivers\\viofs.sys"),
+/* GPL */ _T("System32\\drivers\\viogpudo.sys"),
+/* GPL */ _T("System32\\drivers\\vioinput.sys"),
+/* GPL */ _T("System32\\drivers\\viorng.sys"),
+/* GPL */ _T("System32\\drivers\\vioscsi.sys"),
+/* GPL */ _T("System32\\drivers\\vioser.sys"),
+/* GPL */ _T("System32\\drivers\\viostor.sys")
+/* GPL */ } };
+/* GPL */
+/* GPL */ TCHAR szWinDir[MAX_PATH] = _T("");
+/* GPL */ TCHAR szPath[MAX_PATH] = _T("");
+/* GPL */ PVOID OldValue = NULL;
+/* GPL */
+/* GPL */ if (GetWindowsDirectory(szWinDir, MAX_PATH) == 0) {
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */ if (util::is_wow64()) {
+/* GPL */ Wow64DisableWow64FsRedirection(&OldValue);
+/* GPL */ }
+/* GPL */
+/* GPL */ bool is_vm = false;
+/* GPL */
+/* GPL */ for (const auto& key : keys) {
+/* GPL */ PathCombine(szPath, szWinDir, key);
+/* GPL */ if (util::exists(szPath)) {
+/* GPL */ is_vm = true;
+/* GPL */ break;
+/* GPL */ }
+/* GPL */ }
+/* GPL */
+/* GPL */ if (util::is_wow64()) {
+/* GPL */ Wow64RevertWow64FsRedirection(&OldValue);
+/* GPL */ }
+/* GPL */
+/* GPL */ return is_vm;
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("KVM_DRIVERS: ", "caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check for KVM directory "Virtio-Win"
+/* GPL */ // @category Windows
+/* GPL */ // @author LordNoteWorthy
+/* GPL */ // @note from Al-Khaser project
+/* GPL */ // @link https://github.com/LordNoteworthy/al-khaser/blob/0f31a3866bafdfa703d2ed1ee1a242ab31bf5ef0/al-khaser/AntiVM/KVM.cpp
+/* GPL */ [[nodiscard]] static bool kvm_directories() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ TCHAR szProgramFile[MAX_PATH];
+/* GPL */ TCHAR szPath[MAX_PATH] = _T("");
+/* GPL */ TCHAR szTarget[MAX_PATH] = _T("Virtio-Win\\");
+/* GPL */
+/* GPL */ if (util::is_wow64()) {
+/* GPL */ ExpandEnvironmentStrings(_T("%ProgramW6432%"), szProgramFile, ARRAYSIZE(szProgramFile));
+/* GPL */ } else {
+/* GPL */ SHGetSpecialFolderPath(NULL, szProgramFile, CSIDL_PROGRAM_FILES, FALSE);
+/* GPL */ }
+/* GPL */
+/* GPL */ PathCombine(szPath, szProgramFile, szTarget);
+/* GPL */ return util::exists(szPath);
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("KVM_DIRS: ", "caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check if audio device is present
+/* GPL */ // @category Windows
+/* GPL */ // @author CheckPointSW (InviZzzible project)
+/* GPL */ // @link https://github.com/CheckPointSW/InviZzzible/blob/master/SandboxEvasion/helper.cpp
+/* GPL */ // @copyright GPL-3.0
+/* GPL */ [[nodiscard]] static bool check_audio() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ PCWSTR wszfilterName = L"audio_device_random_name";
+/* GPL */
+/* GPL */ if (FAILED(CoInitialize(NULL)))
+/* GPL */ return false;
+/* GPL */
+/* GPL */ IGraphBuilder* pGraph = nullptr;
+/* GPL */ if (FAILED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&pGraph)))
+/* GPL */ return false;
+/* GPL */
+/* GPL */ // First anti-emulation check: If AddFilter is called with NULL as a first argument it should return the E_POINTER error code.
+/* GPL */ // Some emulators may implement unknown COM interfaces in a generic way, so they will probably fail here.
+/* GPL */ if (E_POINTER != pGraph->AddFilter(NULL, wszfilterName))
+/* GPL */ return true;
+/* GPL */
+/* GPL */ // Initializes a simple Audio Renderer, error code is not checked,
+/* GPL */ // but pBaseFilter will be set to NULL upon failure and the code will eventually fail later.
+/* GPL */ IBaseFilter* pBaseFilter = nullptr;
+/* GPL */
+/* GPL */ HRESULT hr = CoCreateInstance(CLSID_AudioRender, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pBaseFilter);
+/* GPL */ if (FAILED(hr)) {
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */ // Adds the previously created Audio Renderer to the Filter Graph, no error checks
+/* GPL */ pGraph->AddFilter(pBaseFilter, wszfilterName);
+/* GPL */
+/* GPL */ // Tries to find the filter that was just added; in case of any previously not checked error (or wrong emulation)
+/* GPL */ // this function won't find the filter and the sandbox/emulator will be successfully detected.
+/* GPL */ IBaseFilter* pBaseFilter2 = nullptr;
+/* GPL */ pGraph->FindFilterByName(wszfilterName, &pBaseFilter2);
+/* GPL */ if (nullptr == pBaseFilter2)
+/* GPL */ return true;
+/* GPL */
+/* GPL */ // Checks if info.achName is equal to the previously added filterName, if not - poor API emulation
+/* GPL */ FILTER_INFO info = { 0 };
+/* GPL */ pBaseFilter2->QueryFilterInfo(&info);
+/* GPL */ if (0 != wcscmp(info.achName, wszfilterName))
+/* GPL */ return false;
+/* GPL */
+/* GPL */ // Checks if the API sets a proper IReferenceClock pointer
+/* GPL */ IReferenceClock* pClock = nullptr;
+/* GPL */ if (0 != pBaseFilter2->GetSyncSource(&pClock))
+/* GPL */ return false;
+/* GPL */ if (0 != pClock)
+/* GPL */ return false;
+/* GPL */
+/* GPL */ // Checks if CLSID is different from 0
+/* GPL */ CLSID clsID = { 0 };
+/* GPL */ pBaseFilter2->GetClassID(&clsID);
+/* GPL */ if (clsID.Data1 == 0)
+/* GPL */ return true;
+/* GPL */
+/* GPL */ if (nullptr == pBaseFilter2)
+/* GPL */ return true;
+/* GPL */
+/* GPL */ // Just checks if the call was successful
+/* GPL */ IEnumPins* pEnum = nullptr;
+/* GPL */ if (0 != pBaseFilter2->EnumPins(&pEnum))
+/* GPL */ return true;
+/* GPL */
+/* GPL */ // The reference count returned by AddRef has to be higher than 0
+/* GPL */ if (0 == pBaseFilter2->AddRef())
+/* GPL */ return true;
+/* GPL */
+/* GPL */ return false;
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("AUDIO: ", "caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check for QEMU-specific blacklisted directories
+/* GPL */ // @author LordNoteworthy
+/* GPL */ // @link https://github.com/LordNoteworthy/al-khaser/blob/master/al-khaser/AntiVM/Qemu.cpp
+/* GPL */ // @category Windows
+/* GPL */ // @note from al-khaser project
+/* GPL */ // @copyright GPL-3.0
+/* GPL */ [[nodiscard]] static bool qemu_dir() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ TCHAR szProgramFile[MAX_PATH];
+/* GPL */ TCHAR szPath[MAX_PATH] = _T("");
+/* GPL */
+/* GPL */ const TCHAR* szDirectories[] = {
+/* GPL */ _T("qemu-ga"), // QEMU guest agent.
+/* GPL */ _T("SPICE Guest Tools"), // SPICE guest tools.
+/* GPL */ };
+/* GPL */
+/* GPL */ WORD iLength = sizeof(szDirectories) / sizeof(szDirectories[0]);
+/* GPL */ for (int i = 0; i < iLength; i++) {
+/* GPL */ TCHAR msg[256] = _T("");
+/* GPL */
+/* GPL */ if (util::is_wow64())
+/* GPL */ ExpandEnvironmentStrings(_T("%ProgramW6432%"), szProgramFile, ARRAYSIZE(szProgramFile));
+/* GPL */ else
+/* GPL */ SHGetSpecialFolderPath(NULL, szProgramFile, CSIDL_PROGRAM_FILES, FALSE);
+/* GPL */
+/* GPL */ PathCombine(szPath, szProgramFile, szDirectories[i]);
+/* GPL */
+/* GPL */ if (util::exists(szPath))
+/* GPL */ return core::add(QEMU);
+/* GPL */ }
+/* GPL */
+/* GPL */ return false;
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("QEMU_DIR: ", "caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
+/* GPL */
+/* GPL */
+/* GPL */ // @brief Check for the presence of a mouse device
+/* GPL */ // @category Windows
+/* GPL */ // @author a0rtega
+/* GPL */ // @link https://github.com/a0rtega/pafish/blob/master/pafish/rtt.c
+/* GPL */ // @note from pafish project
+/* GPL */ // @copyright GPL
+/* GPL */ [[nodiscard]] static bool mouse_device() try {
+/* GPL */ #if (!MSVC)
+/* GPL */ return false;
+/* GPL */ #else
+/* GPL */ int res;
+/* GPL */ res = GetSystemMetrics(SM_MOUSEPRESENT);
+/* GPL */ return (res == 0);
+/* GPL */ #endif
+/* GPL */ }
+/* GPL */ catch (...) {
+/* GPL */ debug("MOUSE_DEVICE: caught error, returned false");
+/* GPL */ return false;
+/* GPL */ }
/**
@@ -8022,7 +8036,7 @@ struct VM {
cmp("Malware") ||
cmp("malsand") ||
cmp("ClonePC")
- ) {
+ ) {
return true;
}
@@ -9155,7 +9169,7 @@ struct VM {
struct technique {
u8 points = 0; // this is the certainty score between 0 and 100
std::function run; // this is the technique function itself
- bool spoofable = false; // this is to indicate that the technique can be very easily spoofed (not guaranteed)
+ bool is_spoofable = false; // this is to indicate that the technique can be very easily spoofed (not guaranteed)
};
struct custom_technique {
@@ -9213,7 +9227,7 @@ struct VM {
return false;
}
- [[nodiscard]] static bool is_non_technique_set(const flagset& flags) {
+ [[nodiscard]] static bool is_setting_flag_set(const flagset& flags) {
for (std::size_t i = non_technique_begin; i < non_technique_end; i++) {
if (flags.test(i)) {
return true;
@@ -9239,11 +9253,11 @@ struct VM {
return;
}
- if (!core::is_non_technique_set(flags)) {
+ if (!core::is_setting_flag_set(flags)) {
throw std::invalid_argument("Invalid flag option for function parameter found, either leave it empty or add the VM::DEFAULT flag");
}
- // at this stage, only non-technique flags are asserted to be set
+ // at this stage, only settings technique flags are asserted to be set
if (
flags.test(NO_MEMO) ||
flags.test(HIGH_THRESHOLD) ||
@@ -9260,22 +9274,26 @@ struct VM {
// run every VM detection mechanism in the technique table
static u16 run_all(const flagset& flags, const bool shortcut = false) {
u16 points = 0;
+
const bool memo_enabled = core::is_disabled(flags, NO_MEMO);
const u16 threshold_points = (core::is_enabled(flags, HIGH_THRESHOLD) ? high_threshold_score : 200);
- // for main technique table
+ // loop through technique table, where all the techniques are stored
for (const auto& tmp : technique_table) {
const enum_flags technique_macro = tmp.first;
- const technique tuple = tmp.second;
+ const technique technique_data = tmp.second;
- // check if it's disabled
+ // check if the technique is disabled
if (core::is_disabled(flags, technique_macro)) {
continue;
}
// check if it's spoofable, and whether it's enabled
- if (tuple.spoofable && core::is_disabled(flags, SPOOFABLE)) {
+ if (
+ technique_data.is_spoofable &&
+ core::is_disabled(flags, SPOOFABLE)
+ ) {
continue;
}
@@ -9291,25 +9309,27 @@ struct VM {
}
// run the technique
- const bool result = tuple.run();
+ const bool result = technique_data.run();
// accumulate the points if technique detected a VM
if (result) {
- points += tuple.points;
- }
+ points += technique_data.points;
- /**
- * for things like VM::detect() and VM::percentage(),
- * a score of 200+ is guaranteed to be a VM, so
- * there's no point in running the rest of the techniques
- */
+ // this is specific to VM::detected_count() which returns
+ // the number of techniques that returned a positive
+ detected_count_num++;
+ }
+
+ // for things like VM::detect() and VM::percentage(),
+ // a score of 200+ is guaranteed to be a VM, so
+ // there's no point in running the rest of the techniques
if (shortcut && points >= threshold_points) {
return points;
}
// store the current technique result to the cache
if (memo_enabled) {
- memo::cache_store(technique_macro, result, tuple.points);
+ memo::cache_store(technique_macro, result, technique_data.points);
}
}
@@ -9330,13 +9350,20 @@ struct VM {
* basically what this entire template fuckery does is manage the
* variadic arguments being given through the arg_handler function,
* which could either be a std::bitset, a uint8_t, or a combination
- * of both of them. Thisz will handle both argument types and implement
+ * of both of them. This will handle both argument types and implement
* them depending on what their types are. If it's a std::bitset,
- * do the |= operation. If it's a uint8_t, simply .set() that into
- * the flag_collector bitset. That's the gist of it.
+ * do the |= operation on flag_collector. If it's a uint8_t, simply
+ * .set() that into the flag_collector. That's the gist of it.
*
* Also I won't even deny, the majority of this section was 90% generated
* by chatgpt. Can't be arsed with this C++ templatisation shit.
+ * Like is it really my fault that I have a hard time understanging C++'s
+ * god awful metaprogramming designs? And don't even get me started on SNIFAE.
+ *
+ * You don't need an IQ of 3 digits to realise how dogshit this language
+ * is, when you end up in situations where there's a few correct solutions
+ * to a problem, but with a billion ways you can do the same thing but in
+ * the "wrong" way. I genuinely can't wait for Carbon to come out.
*/
private:
static flagset flag_collector;
@@ -9454,18 +9481,23 @@ struct VM {
#endif
public:
- // Function template to test variadic arguments
+ // fetch the flags, could be an enum value OR a std::bitset.
+ // This will then generate a different std::bitset as the
+ // return value by enabling the bits based on the argument.
template
static flagset arg_handler(Args&&... args) {
- if VMAWARE_CONSTEXPR(is_empty()) {
+ if VMAWARE_CONSTEXPR (is_empty()) {
return DEFAULT;
}
flag_collector.reset();
global_flags.reset();
+ // set the bits in the flag, can take in
+ // either an enum value or a std::bitset
handleArgs(std::forward(args)...);
+ // handle edgecases
core::flag_sanitizer(flag_collector);
global_flags = flag_collector;
@@ -9478,14 +9510,15 @@ struct VM {
static flagset disabled_arg_handler(Args&&... args) {
flag_collector.reset();
- if VMAWARE_CONSTEXPR(is_empty()) {
- throw std::invalid_argument("VM::DISABLE must contain a flag");
+ if VMAWARE_CONSTEXPR (is_empty()) {
+ throw std::invalid_argument("VM::DISABLE() must contain a flag");
}
handle_disabled_args(std::forward(args)...);
- if (core::is_non_technique_set(flag_collector)) {
- throw std::invalid_argument("VM::DISABLE must not contain a non-technique flag, they are disabled by default anyway");
+ // check if a settings flag is set, which is not valid
+ if (core::is_setting_flag_set(flag_collector)) {
+ throw std::invalid_argument("VM::DISABLE() must not contain a settings flag, they are disabled by default anyway");
}
return flag_collector;
@@ -9522,7 +9555,7 @@ struct VM {
throw_error("Flag argument must be a valid");
}
- // check if the bit is a non-technique flag, which shouldn't be allowed
+ // check if the bit is a settings flag, which shouldn't be allowed
if (
(flag_bit == NO_MEMO) ||
(flag_bit == HIGH_THRESHOLD) ||
@@ -9552,6 +9585,10 @@ struct VM {
const core::technique& pair = it->second;
const bool result = pair.run();
+ if (result) {
+ detected_count_num++;
+ }
+
#ifdef __VMAWARE_DEBUG__
total_points += pair.points;
#endif
@@ -9573,15 +9610,17 @@ struct VM {
[[nodiscard]] static std::string brand(Args ...args) {
flagset flags = core::arg_handler(args...);
+ // is the multiple setting flag enabled? (meaning multiple
+ // brand strings will be outputted if there's a conflict)
const bool is_multiple = core::is_enabled(flags, MULTIPLE);
- // are all the techiques already run? if not, run all of them to get the necessary info to fetch the brand
+ // are all the techiques already run? if not, run them
+ // to fetch the necessary info to determine the brand
if (!memo::all_present() || core::is_enabled(flags, NO_MEMO)) {
- u16 tmp = core::run_all(flags);
- UNUSED(tmp);
+ core::run_all(flags);
}
- // check if it's already cached and return that instead
+ // check if the result is already cached and return that instead
if (core::is_disabled(flags, NO_MEMO)) {
if (is_multiple) {
if (memo::multi_brand::is_cached()) {
@@ -9596,7 +9635,8 @@ struct VM {
}
}
- // goofy ass C++11 and C++14 linker error workaround
+ // goofy ass C++11 and C++14 linker error workaround,
+ // and yes, this does look indeed stupid.
#if (CPP <= 14)
constexpr const char* TMP_QEMU = "QEMU";
constexpr const char* TMP_KVM = "KVM";
@@ -9639,27 +9679,42 @@ struct VM {
constexpr const char* TMP_HYPERV_ARTIFACT = HYPERV_ARTIFACT;
#endif
+ // this is where all the RELEVANT brands are stored.
+ // The ones with no points will be filtered out.
std::map brands;
+ // add the relevant brands with at least 1 point
for (const auto &element : core::brand_scoreboard) {
if (element.second > 0) {
brands.insert(std::make_pair(element.first, element.second));
}
}
- // if no brand had a single point, return "Unknown"
+ // if all brands had a point of 0, return
+ // "Unknown" (no relevant brands were found)
if (brands.empty()) {
return "Unknown";
}
+ // if there's only a single brand, return it.
+ // This will skip the rest of the function
+ // where it will process and merge certain
+ // brands
if (brands.size() == 1) {
return brands.begin()->first;
- } else if (brands.size() > 1) {
+ }
+
+ // remove Hyper-V artifacts if found with other
+ // brands, because that's not a VM. It's added
+ // only for the sake of information cuz of the
+ // fucky wucky Hyper-V problem (see Hyper-X)
+ if (brands.size() > 1) {
if (brands.find(TMP_HYPERV_ARTIFACT) != brands.end()) {
brands.erase(TMP_HYPERV_ARTIFACT);
}
}
+ // merge 2 brands, and make a single brand out of it.
auto merger = [&](const char* a, const char* b, const char* result) -> void {
if (
(brands.count(a) > 0) &&
@@ -9671,6 +9726,7 @@ struct VM {
}
};
+ // same as above, but for 3
auto triple_merger = [&](const char* a, const char* b, const char* c, const char* result) -> void {
if (
(brands.count(a) > 0) &&
@@ -9684,6 +9740,8 @@ struct VM {
}
};
+ // some edgecase handling for Hyper-V and VirtualPC since
+ // they're very similar, and they're both from Microsoft (ew)
if ((brands.count(TMP_HYPERV) > brands.count(TMP_VPC))) {
brands.erase(TMP_VPC);
} else if (brands.count(TMP_HYPERV) < brands.count(TMP_VPC)) {
@@ -9695,9 +9753,19 @@ struct VM {
merger(TMP_VPC, TMP_HYPERV, TMP_HYPERV_VPC);
}
- merger(TMP_HYPERV, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT);
- merger(TMP_VPC, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT);
- merger(TMP_HYPERV_VPC, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT);
+
+ // this is the section where brand post-processing will be done.
+ // The reason why this part is necessary is because it will
+ // output a more accurate picture on the VM brand. For example,
+ // Azure's cloud is based on Hyper-V, but Hyper-V may have
+ // a higher score due to the prevalence of it in a practical
+ // setting, which will put Azure to the side. This is stupid
+ // because there should be an indication that Azure is involved
+ // since it's a better idea to let the end-user know that the
+ // brand is "Azure Hyper-V" instead of just "Hyper-V". So what
+ // this section does is "merge" the brands together to form
+ // a more accurate idea of the brand(s) involved.
+
merger(TMP_AZURE, TMP_HYPERV, TMP_AZURE);
merger(TMP_AZURE, TMP_VPC, TMP_AZURE);
@@ -9723,8 +9791,11 @@ struct VM {
merger(TMP_VMWARE, TMP_GSX, TMP_GSX);
merger(TMP_VMWARE, TMP_WORKSTATION, TMP_WORKSTATION);
+ // the brand element, which stores the NAME (const char*) and the SCORE (u8)
using brand_element_t = std::pair;
+ // sort the "brands" map so that the brands with the
+ // highest score appears first in descending order
auto sorter = [&]() -> std::vector {
std::vector vec(brands.begin(), brands.end());
@@ -9741,11 +9812,15 @@ struct VM {
std::vector vec = sorter();
std::string ret_str = "Unknown";
+ // if the multiple setting flag is NOT set, return the
+ // brand with the highest score. Else, return a std::string
+ // of the brand message (i.e. "VirtualBox or VMware").
+ // See VM::MULTIPLE flag in docs for more information.
if (!is_multiple) {
ret_str = vec.front().first;
} else {
std::stringstream ss;
- u8 i = 1;
+ std::size_t i = 1;
ss << vec.front().first;
for (; i < vec.size(); i++) {
@@ -9755,6 +9830,7 @@ struct VM {
ret_str = ss.str();
}
+ // cache the result if memoization is enabled
if (core::is_disabled(flags, NO_MEMO)) {
if (is_multiple) {
core_debug("VM::brand(): cached multiple brand string");
@@ -9765,7 +9841,7 @@ struct VM {
}
}
- // this gets annoying really fast
+ // debug stuff to see the brand scoreboard, ignore this
#ifdef __VMAWARE_DEBUG__
for (const auto p : brands) {
core_debug("scoreboard: ", (int)p.second, " : ", p.first);
@@ -9784,23 +9860,26 @@ struct VM {
*/
template
static bool detect(Args ...args) {
+ // fetch all the flags in a std::bitset
flagset flags = core::arg_handler(args...);
+ // run all the techniques based on the
+ // flags above, and get a total score
const u16 points = core::run_all(flags, SHORTCUT);
#if (CPP >= 23)
[[assume(points < maximum_points)]];
#endif
- bool result = false;
+ u16 threshold = 150;
+ // if high threshold is set, the points
+ // will be 300. If not, leave it as 150.
if (core::is_enabled(flags, HIGH_THRESHOLD)) {
- result = (points >= high_threshold_score);
- } else {
- result = (points >= 150);
+ threshold = high_threshold_score;
}
- return result;
+ return (points >= threshold);
}
@@ -9812,21 +9891,29 @@ struct VM {
*/
template
static u8 percentage(Args ...args) {
+ // fetch all the flags in a std::bitset
const flagset flags = core::arg_handler(args...);
+ // run all the techniques based on the
+ // flags above, and get a total score
const u16 points = core::run_all(flags, SHORTCUT);
- u8 percent = 0;
#if (CPP >= 23)
[[assume(points < maximum_points)]];
#endif
+ u8 percent = 0;
u16 threshold = 150;
+ // set to 300 if high threshold is enabled
if (core::is_enabled(flags, HIGH_THRESHOLD)) {
threshold = high_threshold_score;
}
+ // the percentage will be set to 99%, because a score
+ // of 100 is not entirely robust. 150 is more robust
+ // in my opinion, which is why you need a score of
+ // above 150 to get to 100%
if (points >= threshold) {
percent = 100;
} else if (points >= 100) {
@@ -9853,6 +9940,7 @@ struct VM {
, const std::source_location& loc = std::source_location::current()
#endif
) {
+ // lambda to throw the error
auto throw_error = [&](const char* text) -> void {
std::stringstream ss;
#if (CPP >= 20 && !CLANG)
@@ -9870,11 +9958,13 @@ struct VM {
[[assume(percent > 0 && percent <= 100)]];
#endif
+ // generate the custom technique struct
core::custom_technique query{
percent,
detection_func
};
+ // push it to the custome_table vector
core::custom_table.emplace_back(query);
}
@@ -9887,6 +9977,8 @@ struct VM {
*/
template
static flagset DISABLE(Args ...args) {
+ // basically core::arg_handler but in reverse,
+ // it'll clear the bits of the provided flags
flagset flags = core::disabled_arg_handler(args...);
flags.flip();
@@ -9902,7 +9994,7 @@ struct VM {
/**
* @brief This will convert the technique flag into a string, which will correspond to the technique name
* @param single technique flag in VM structure
- * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️
+ * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️
*/
[[nodiscard]] static std::string flag_to_string(const enum_flags flag) {
switch (flag) {
@@ -9934,18 +10026,18 @@ struct VM {
case DISK_SIZE: return "DISK_SIZE";
case VBOX_DEFAULT: return "VBOX_DEFAULT";
case VBOX_NETWORK: return "VBOX_NETWORK";
- case COMPUTER_NAME: return "COMPUTER_NAME";
- case WINE_CHECK: return "WINE_CHECK";
- case HOSTNAME: return "HOSTNAME";
- case MEMORY: return "MEMORY";
- case VBOX_WINDOW_CLASS: return "VBOX_WINDOW_CLASS";
- case LOADED_DLLS: return "LOADED_DLLS";
- case KVM_REG: return "KVM_REG";
- case KVM_DRIVERS: return "KVM_DRIVERS";
- case KVM_DIRS: return "KVM_DIRS";
- case AUDIO: return "AUDIO";
- case QEMU_DIR: return "QEMU_DIR";
- case MOUSE_DEVICE: return "MOUSE_DEVICE";
+/* GPL */ case COMPUTER_NAME: return "COMPUTER_NAME";
+/* GPL */ case WINE_CHECK: return "WINE_CHECK";
+/* GPL */ case HOSTNAME: return "HOSTNAME";
+/* GPL */ case MEMORY: return "MEMORY";
+/* GPL */ case VBOX_WINDOW_CLASS: return "VBOX_WINDOW_CLASS";
+/* GPL */ case LOADED_DLLS: return "LOADED_DLLS";
+/* GPL */ case KVM_REG: return "KVM_REG";
+/* GPL */ case KVM_DRIVERS: return "KVM_DRIVERS";
+/* GPL */ case KVM_DIRS: return "KVM_DIRS";
+/* GPL */ case AUDIO: return "AUDIO";
+/* GPL */ case QEMU_DIR: return "QEMU_DIR";
+/* GPL */ case MOUSE_DEVICE: return "MOUSE_DEVICE";
case VM_PROCESSES: return "VM_PROCESSES";
case LINUX_USER_HOST: return "LINUX_USER_HOST";
case GAMARUE: return "GAMARUE";
@@ -10028,9 +10120,9 @@ struct VM {
/**
- * @brief return a vector of detected brand strings (DEVELOPMENT FUNCTION, NOT MEANT FOR PUBLIC USE)
+ * @brief return a vector of detected brand strings
* @param any flag combination in VM structure or nothing
- * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️
+ * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️
*/
template
static std::map brand_map(Args ...args) {
@@ -10038,8 +10130,7 @@ struct VM {
// are all the techiques already run? if not, run all of them to get the necessary info to fetch the brand
if (!memo::all_present() || core::is_enabled(flags, NO_MEMO)) {
- u16 tmp = core::run_all(flags);
- UNUSED(tmp);
+ core::run_all(flags);
}
return core::brand_scoreboard;
@@ -10050,16 +10141,17 @@ struct VM {
* @brief Change the certainty score of a technique
* @param technique flag, then the new percentage score to overwite
* @return void
- * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️
+ * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️
*/
static void modify_score(
const enum_flags flag,
- const std::uint8_t percent
+ const u8 percent
// clang doesn't support std::source_location for some reason
#if (CPP >= 20 && !CLANG)
, const std::source_location& loc = std::source_location::current()
#endif
) {
+ // lambda to throw the error
auto throw_error = [&](const char* text) -> void {
std::stringstream ss;
#if (CPP >= 20 && !CLANG)
@@ -10077,19 +10169,196 @@ struct VM {
[[assume(percent <= 100)]];
#endif
+ // check if the flag provided is a setting flag, which isn't valid.
if (static_cast(flag) >= technique_end) {
throw_error("The flag is not a technique flag");
}
+ // replica type alias of the technique table
using table_t = std::map;
auto modify = [](table_t &table, const enum_flags flag, const u8 percent) -> void {
core::technique &tmp = table.at(flag);
- table[flag] = { percent, tmp.run, tmp.spoofable };
+ table[flag] = { percent, tmp.run, tmp.is_spoofable };
};
modify(const_cast(core::technique_table), flag, percent);
}
+
+
+ /**
+ * @brief Fetch the total number of detected techniques
+ * @param any flag combination in VM structure or nothing
+ * @return std::uint8_t
+ */
+ template
+ static u8 detected_count(Args ...args) {
+ flagset flags = core::arg_handler(args...);
+
+ // run all the techniques, which will set the detected_count variable
+ core::run_all(flags);
+
+ return detected_count_num;
+ }
+
+
+ /**
+ * @brief Fetch the total number of detected techniques
+ * @param any flag combination in VM structure or nothing
+ * @return std::uint8_t
+ */
+ template
+ static std::string type(Args ...args) {
+ flagset flags = core::arg_handler(args...);
+
+ const std::string brand_str = brand(flags);
+
+ // if multiple brands were found, return unknown
+ if (util::find(brand_str, " or ")) {
+ return "Unknown";
+ }
+
+ const std::map type_table {
+ // type 1
+ { XEN, "Hypervisor (type 1)" },
+ { VMWARE_ESX, "Hypervisor (type 1)" },
+ { ACRN, "Hypervisor (type 1)" },
+ { QNX, "Hypervisor (type 1)" },
+ { HYPERV, "Hypervisor (type 1)" },
+ { AZURE_HYPERV, "Hypervisor (type 1)" },
+ { NANOVISOR, "Hypervisor (type 1)" },
+ { KVM, "Hypervisor (type 1)" },
+ { BHYVE, "Hypervisor (type 1)" },
+ { KVM_HYPERV, "Hypervisor (type 1)" },
+ { QEMU_KVM_HYPERV, "Hypervisor (type 1)" },
+ { QEMU_KVM, "Hypervisor (type 1)" },
+ { INTEL_HAXM, "Hypervisor (type 1)" },
+ { INTEL_KGT, "Hypervisor (type 1)" },
+ { SIMPLEVISOR, "Hypervisor (type 1)" },
+ { GCE, "Hypervisor (type 1)" },
+ { OPENSTACK, "Hypervisor (type 1)" },
+ { KUBEVIRT, "Hypervisor (type 1)" },
+ { POWERVM, "Hypervisor (type 1)" },
+ { AWS_NITRO, "Hypervisor (type 1)" },
+
+ // type 2
+ { VBOX, "Hypervisor (type 2)" },
+ { VMWARE, "Hypervisor (type 2)" },
+ { VMWARE_EXPRESS, "Hypervisor (type 2)" },
+ { VMWARE_GSX, "Hypervisor (type 2)" },
+ { VMWARE_WORKSTATION, "Hypervisor (type 2)" },
+ { VMWARE_FUSION, "Hypervisor (type 2)" },
+ { PARALLELS, "Hypervisor (type 2)" },
+ { VPC, "Hypervisor (type 2)" },
+ { NVMM, "Hypervisor (type 2)" },
+ { BSD_VMM, "Hypervisor (type 2)" },
+
+ // sandbox
+ { CUCKOO, "Sandbox" },
+ { SANDBOXIE, "Sandbox" },
+ { HYBRID, "Sandbox" },
+ { CWSANDBOX, "Sandbox" },
+ { JOEBOX, "Sandbox" },
+ { ANUBIS, "Sandbox" },
+ { COMODO, "Sandbox" },
+ { THREATEXPERT, "Sandbox" },
+ { ANYRUN, "Sandbox"},
+
+ // misc
+ { BOCHS, "Emulator" },
+ { BLUESTACKS, "Emulator" },
+ { MSXTA, "Emulator" },
+ { QEMU, "Emulator/Hypervisor (type 2)" },
+ { JAILHOUSE, "Partitioning Hypervisor" },
+ { UNISYS, "Partitioning Hypervisor" },
+ { DOCKER, "Container" },
+ { PODMAN, "Container" },
+ { OPENVZ, "Container" },
+ { HYPERV_VPC, "Hypervisor (either type 1 or 2)" },
+ { LMHS, "Hypervisor (unknown type)" },
+ { WINE, "Compatibility layer" },
+ { APPLE_VZ, "Unknown" },
+ { HYPERV_ARTIFACT, "Unknown" },
+ { UML, "Paravirtualised/Hypervisor (type 2)" },
+ { WSL, "Hybrid Hyper-V (type 1 and 2)" }, // debatable tbh
+ { APPLE_ROSETTA, "Binary Translation Layer/Emulator" },
+ };
+
+ auto it = type_table.find(brand_str.c_str());
+
+ if (it != type_table.end()) {
+ return it->second;
+ }
+
+ return "Unknown";
+ }
+
+
+ /**
+ * @brief Fetch the conclusion message based on the brand and percentage
+ * @param any flag combination in VM structure or nothing
+ * @return std::string
+ */
+ template
+ static std::string conclusion(Args ...args) {
+ flagset flags = core::arg_handler(args...);
+
+ const std::string brand_tmp = brand(flags);
+ const u8 percent_tmp = percentage(flags);
+
+ constexpr const char* baremetal = "Running in baremetal";
+ constexpr const char* very_unlikely = "Very unlikely a VM";
+ constexpr const char* unlikely = "Unlikely a VM";
+
+ std::string potentially = "Potentially a VM";
+ std::string might = "Might be a VM";
+ std::string likely = "Likely a VM";
+ std::string very_likely = "Very likely a VM";
+ std::string inside_vm = "Running inside a VM";
+
+ if (brand_tmp != "Unknown") {
+ potentially = "Potentially a " + brand_tmp + " VM";
+ might = "Might be a " + brand_tmp + " VM";
+ likely = "Likely a " + brand_tmp + " VM";
+ very_likely = "Very likely a " + brand_tmp + " VM";
+ inside_vm = "Running inside a " + brand_tmp + " VM";
+ }
+
+ if (percent_tmp == 0) { return baremetal; }
+ else if (percent_tmp <= 20) { return very_unlikely; }
+ else if (percent_tmp <= 35) { return unlikely; }
+ else if (percent_tmp < 50) { return potentially; }
+ else if (percent_tmp <= 62) { return might; }
+ else if (percent_tmp <= 75) { return likely; }
+ else if (percent_tmp < 100) { return very_likely; }
+ else { return inside_vm; }
+ }
+
+
+ struct vmaware {
+ std::string brand;
+ std::string type;
+ std::string conclusion;
+ bool is_vm;
+ u8 percentage;
+ u8 detected_count;
+ u8 technique_count;
+
+ vmaware() = default;
+
+ template
+ vmaware(Args ...args) {
+ flagset flags = core::arg_handler(args...);
+
+ brand = VM::brand(flags);
+ type = VM::type(flags);
+ conclusion = VM::conclusion(flags);
+ is_vm = VM::detect(flags);
+ percentage = VM::percentage(flags);
+ detected_count = VM::detected_count(flags);
+ technique_count = VM::technique_count;
+ }
+ };
};
MSVC_ENABLE_WARNING(ASSIGNMENT_OPERATOR NO_INLINE_FUNC SPECTRE)
@@ -10174,12 +10443,22 @@ bool VM::memo::hyperv::is_stored = false;
VM::u16 VM::total_points = 0;
#endif
-// not even sure how to explain honestly, just pretend these don't exist idfk
+// these are basically the base values for the core::arg_handler function.
+// It's like a bucket that will collect all the bits enabled. If for example
+// VM::detect(VM::HIGH_THRESHOLD) is passed, the HIGH_THRESHOLD bit will be
+// collected in this flagset (std::bitset) variable, and eventually be the
+// return value for actual end-user functions like VM::detect() to rely
+// and work on. VM::global_flags is just a copy of the flags but visible
+// globally throughout the whole VM struct, as the name implies.
VM::flagset VM::core::flag_collector;
VM::flagset VM::global_flags;
+
+VM::u8 VM::detected_count_num = 0;
+
+
// default flags
-VM::flagset VM::DEFAULT = []() -> flagset {
+VM::flagset VM::DEFAULT = []() noexcept -> flagset {
flagset tmp;
// set all bits to 1
@@ -10190,7 +10469,7 @@ VM::flagset VM::DEFAULT = []() -> flagset {
tmp.flip(RDTSC);
tmp.flip(RDTSC_VMEXIT);
- // disable all the non-technique flags
+ // disable all the settings flags
tmp.flip(NO_MEMO);
tmp.flip(HIGH_THRESHOLD);
tmp.flip(SPOOFABLE);
@@ -10201,13 +10480,13 @@ VM::flagset VM::DEFAULT = []() -> flagset {
// flag to enable every technique
-VM::flagset VM::ALL = []() -> flagset {
+VM::flagset VM::ALL = []() noexcept -> flagset {
flagset tmp;
// set all bits to 1
tmp.set();
- // disable all the non-technique flags (except SPOOFABLE)
+ // disable all the settings technique flags (except SPOOFABLE)
tmp.flip(NO_MEMO);
tmp.flip(HIGH_THRESHOLD);
tmp.flip(MULTIPLE);
@@ -10252,10 +10531,9 @@ std::vector VM::core::custom_table = {
};
-
// the 0~100 points are debatable, but I think it's fine how it is. Feel free to disagree.
const std::map VM::core::technique_table = {
- // FORMAT: VM:: = { certainty%, function pointer, is spoofable? }
+ // FORMAT: { VM::, { certainty%, function pointer, is spoofable? } },
{ VM::VMID, { 100, VM::vmid, false } },
{ VM::CPU_BRAND, { 50, VM::cpu_brand, false } },
@@ -10285,18 +10563,18 @@ const std::map VM::core::technique_table =
{ VM::DISK_SIZE, { 60, VM::disk_size, false } },
{ VM::VBOX_DEFAULT, { 55, VM::vbox_default_specs, false } },
{ VM::VBOX_NETWORK, { 70, VM::vbox_network_share, false } },
- { VM::COMPUTER_NAME, { 15, VM::computer_name_match, true } }, // GPL
- { VM::WINE_CHECK, { 85, VM::wine, false } }, // GPL
- { VM::HOSTNAME, { 25, VM::hostname_match, true } }, // GPL
- { VM::MEMORY, { 35, VM::low_memory_space, false } }, // GPL
- { VM::VBOX_WINDOW_CLASS, { 10, VM::vbox_window_class, false } }, // GPL
- { VM::LOADED_DLLS, { 75, VM::loaded_dlls, true } }, // GPL
- { VM::KVM_REG, { 75, VM::kvm_registry, true } }, // GPL
- { VM::KVM_DRIVERS, { 55, VM::kvm_drivers, true } }, // GPL
- { VM::KVM_DIRS, { 55, VM::kvm_directories, true } }, // GPL
- { VM::AUDIO, { 35, VM::check_audio, false } }, // GPL
- { VM::QEMU_DIR, { 45, VM::qemu_dir, true } }, // GPL
- { VM::MOUSE_DEVICE, { 20, VM::mouse_device, true } }, // GPL
+/* GPL */ { VM::COMPUTER_NAME, { 15, VM::computer_name_match, true } },
+/* GPL */ { VM::WINE_CHECK, { 85, VM::wine, false } },
+/* GPL */ { VM::HOSTNAME, { 25, VM::hostname_match, true } },
+/* GPL */ { VM::MEMORY, { 35, VM::low_memory_space, false } },
+/* GPL */ { VM::VBOX_WINDOW_CLASS, { 10, VM::vbox_window_class, false } },
+/* GPL */ { VM::LOADED_DLLS, { 75, VM::loaded_dlls, true } },
+/* GPL */ { VM::KVM_REG, { 75, VM::kvm_registry, true } },
+/* GPL */ { VM::KVM_DRIVERS, { 55, VM::kvm_drivers, true } },
+/* GPL */ { VM::KVM_DIRS, { 55, VM::kvm_directories, true } },
+/* GPL */ { VM::AUDIO, { 35, VM::check_audio, false } },
+/* GPL */ { VM::QEMU_DIR, { 45, VM::qemu_dir, true } },
+/* GPL */ { VM::MOUSE_DEVICE, { 20, VM::mouse_device, true } },
{ VM::VM_PROCESSES, { 30, VM::vm_processes, true } },
{ VM::LINUX_USER_HOST, { 25, VM::linux_user_host, true } },
{ VM::GAMARUE, { 40, VM::gamarue, true } },
@@ -10341,7 +10619,7 @@ const std::map VM::core::technique_table =
{ VM::MUTEX, { 85, VM::mutex, false } },
{ VM::UPTIME, { 10, VM::uptime, true } },
{ VM::ODD_CPU_THREADS, { 80, VM::odd_cpu_threads, false } },
- { VM::INTEL_THREAD_MISMATCH, { 85, VM::intel_thread_mismatch, false } },
+ { VM::INTEL_THREAD_MISMATCH, { 60, VM::intel_thread_mismatch, false } },
{ VM::XEON_THREAD_MISMATCH, { 85, VM::xeon_thread_mismatch, false } },
{ VM::NETTITUDE_VM_MEMORY, { 75, VM::nettitude_vm_memory, false } },
{ VM::CPUID_BITSET, { 20, VM::cpuid_bitset, false } },
diff --git a/src/vmaware_MIT.hpp b/src/vmaware_MIT.hpp
index 2d19e84..b9aa722 100644
--- a/src/vmaware_MIT.hpp
+++ b/src/vmaware_MIT.hpp
@@ -45,14 +45,14 @@
*
*
* ================================ SECTIONS ==================================
- * - enums for publicly accessible techniques => line 322
- * - struct for internal cpu operations => line 581
- * - struct for internal memoization => line 1007
- * - struct for internal utility functions => line 1134
- * - struct for internal core components => line 9152
- * - start of internal VM detection techniques => line 2409
- * - start of public VM detection functions => line 9495
- * - start of externally defined variables => line 10095
+ * - enums for publicly accessible techniques => line 346
+ * - struct for internal cpu operations => line 598
+ * - struct for internal memoization => line 1024
+ * - struct for internal utility functions => line 1152
+ * - struct for internal core components => line 8712
+ * - start of internal VM detection techniques => line 2448
+ * - start of public VM detection functions => line 9074
+ * - start of externally defined variables => line 9900
*
*
* ================================ EXAMPLE ==================================
@@ -71,6 +71,8 @@
* }
*/
+#pragma once
+
#if (defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64) || defined(__MINGW32__))
#define MSVC 1
#define LINUX 0
@@ -448,7 +450,7 @@ struct VM {
ANYRUN_DRIVER,
ANYRUN_DIRECTORY,
- // start of non-technique flags (THE ORDERING IS VERY SPECIFIC HERE AND MIGHT BREAK SOMETHING IF RE-ORDERED)
+ // start of settings technique flags (THE ORDERING IS VERY SPECIFIC HERE AND MIGHT BREAK SOMETHING IF RE-ORDERED)
NO_MEMO,
HIGH_THRESHOLD,
NULL_ARG, // does nothing, just a placeholder flag mainly for the CLI
@@ -458,7 +460,7 @@ struct VM {
private:
static constexpr u8 enum_size = MULTIPLE; // get enum size through value of last element
- static constexpr u8 non_technique_count = MULTIPLE - NO_MEMO + 1; // get number of non-technique flags like VM::NO_MEMO for example
+ static constexpr u8 non_technique_count = MULTIPLE - NO_MEMO + 1; // get number of settings technique flags like VM::NO_MEMO for example
static constexpr u8 INVALID = 255; // explicit invalid technique macro
static constexpr u16 maximum_points = 4765; // theoretical total points if all VM detections returned true (which is practically impossible)
static constexpr u16 high_threshold_score = 300; // new threshold score from 100 to 350 if VM::HIGH_THRESHOLD flag is enabled
@@ -473,6 +475,11 @@ struct VM {
static constexpr u8 non_technique_begin = NO_MEMO;
static constexpr u8 non_technique_end = enum_end;
+
+ // this is specifically meant for VM::detected_count() to
+ // get the total number of techniques that detected a VM
+ static u8 detected_count_num;
+
public:
static constexpr u8 technique_count = NO_MEMO; // get total number of techniques
static std::vector technique_vector;
@@ -1141,6 +1148,7 @@ struct VM {
};
};
+
// miscellaneous functionalities
struct util {
#if (LINUX)
@@ -1173,7 +1181,7 @@ struct VM {
}
std::vector buffer((std::istreambuf_iterator(file)),
- std::istreambuf_iterator());
+ std::istreambuf_iterator());
file.close();
@@ -1223,7 +1231,7 @@ struct VM {
return (
(uid != euid) ||
(euid == 0)
- );
+ );
#elif (MSVC)
BOOL is_admin = FALSE;
HANDLE hToken = NULL;
@@ -1681,7 +1689,16 @@ struct VM {
/**
* @brief Checks whether Hyper-V host artifacts are present instead of an actual Hyper-V VM
- * @note idea and credits to Requiem (https://github.com/NotRequiem)
+ * @note Hyper-V has an obscure feature where if it's enabled in the host system, the CPU
+ * hardware values makes it look like the whole system is running inside Hyper-V,
+ * which isn't true. This makes it a challenge to determine whether the hardware
+ * values the library is collecting is either a real Hyper-V VM, or just the artifacts
+ * of what Hyper-V has left as a consequence of having it enabled in the host system.
+ * The reason why this is a problem is because the library might falsely conclude that
+ * your the host system is running in Hyper-V, which is a false positive. This is where
+ * the Hyper-X mechanism comes into play to distinguish between these two.
+ * @author idea by Requiem (https://github.com/NotRequiem)
+ * @link graph to explain how this works: https://github.com/kernelwernel/VMAware/blob/main/assets/Hyper-X.png
*/
[[nodiscard]] static bool hyper_x() {
#if (!MSVC)
@@ -1702,23 +1719,35 @@ struct VM {
return result;
};
- char out[sizeof(int32_t) * 4 + 1] = { 0 }; // e*x size + number of e*x registers + null terminator
- cpu::cpuid((int*)out, cpu::leaf::hypervisor);
+ auto root_partition = []() -> bool {
+ u32 ebx, unused = 0;
+ cpu::cpuid(unused, ebx, unused, unused, 0x40000003);
+ return (ebx & 1);
+ };
- const u32 eax = static_cast(out[0]);
+ auto eax = []() -> bool {
+ char out[sizeof(int32_t) * 4 + 1] = { 0 }; // e*x size + number of e*x registers + null terminator
+ cpu::cpuid((int*)out, cpu::leaf::hypervisor);
- core_debug("HYPER_X: eax = ", eax);
+ const u32 eax = static_cast(out[0]);
- const bool is_eax_valid = ((eax == 11) || (eax == 12));
+ core_debug("HYPER_X: eax = ", eax);
- const std::array cpu = cpu::cpu_manufacturer(cpu::leaf::hypervisor);
+ return ((eax == 11) || (eax == 12));
+ };
- const bool is_cpu_hyperv = (
- (cpu.at(0) == "Microsoft Hv") ||
- (cpu.at(1) == "Microsoft Hv")
- );
-
- if (is_eax_valid || is_cpu_hyperv) {
+ auto cpu_vmid = []() -> bool {
+ const std::array cpu = cpu::cpu_manufacturer(cpu::leaf::hypervisor);
+
+ return (
+ (cpu.at(0) == "Microsoft Hv") ||
+ (cpu.at(1) == "Microsoft Hv")
+ );
+ };
+
+ const u8 points = (root_partition() + eax() + cpu_vmid());
+
+ if (points >= 2) {
// SMBIOS check
const std::string smbios = SMBIOS_string();
@@ -2006,27 +2035,27 @@ struct VM {
typedef NTSTATUS(WINAPI* RtlGetVersionFunc)(PRTL_OSVERSIONINFOW);
const std::map windowsVersions = {
- { 6002, 6 }, // windows vista, technically no number but this function is just for great than operations anyway so it doesn't matter
- { 7601, 7 },
- { 9200, 8 },
- { 9600, 8 },
- { 10240, 10 },
- { 10586, 10 },
- { 14393, 10 },
- { 15063, 10 },
- { 16299, 10 },
- { 17134, 10 },
- { 17763, 10 },
- { 18362, 10 },
- { 18363, 10 },
- { 19041, 10 },
- { 19042, 10 },
- { 19043, 10 },
- { 19044, 10 },
- { 19045, 10 },
- { 22000, 11 },
- { 22621, 11 },
- { 22631, 11 }
+ { 6002, static_cast(6) }, // windows vista, technically no number but this function is just for great than operations anyway so it doesn't matter
+ { 7601, static_cast(7) },
+ { 9200, static_cast(8) },
+ { 9600, static_cast(8) },
+ { 10240, static_cast(10) },
+ { 10586, static_cast(10) },
+ { 14393, static_cast(10) },
+ { 15063, static_cast(10) },
+ { 16299, static_cast(10) },
+ { 17134, static_cast(10) },
+ { 17763, static_cast(10) },
+ { 18362, static_cast(10) },
+ { 18363, static_cast(10) },
+ { 19041, static_cast(10) },
+ { 19042, static_cast(10) },
+ { 19043, static_cast(10) },
+ { 19044, static_cast(10) },
+ { 19045, static_cast(10) },
+ { 22000, static_cast(11) },
+ { 22621, static_cast(11) },
+ { 22631, static_cast(11) }
};
HMODULE ntdll = LoadLibraryW(L"ntdll.dll");
@@ -3621,6 +3650,8 @@ struct VM {
}
+
+
/**
* @brief Check for any VM processes that are active
* @category Windows
@@ -7551,7 +7582,7 @@ struct VM {
cmp("Malware") ||
cmp("malsand") ||
cmp("ClonePC")
- ) {
+ ) {
return true;
}
@@ -8684,7 +8715,7 @@ struct VM {
struct technique {
u8 points = 0; // this is the certainty score between 0 and 100
std::function run; // this is the technique function itself
- bool spoofable = false; // this is to indicate that the technique can be very easily spoofed (not guaranteed)
+ bool is_spoofable = false; // this is to indicate that the technique can be very easily spoofed (not guaranteed)
};
struct custom_technique {
@@ -8742,7 +8773,7 @@ struct VM {
return false;
}
- [[nodiscard]] static bool is_non_technique_set(const flagset& flags) {
+ [[nodiscard]] static bool is_setting_flag_set(const flagset& flags) {
for (std::size_t i = non_technique_begin; i < non_technique_end; i++) {
if (flags.test(i)) {
return true;
@@ -8768,11 +8799,11 @@ struct VM {
return;
}
- if (!core::is_non_technique_set(flags)) {
+ if (!core::is_setting_flag_set(flags)) {
throw std::invalid_argument("Invalid flag option for function parameter found, either leave it empty or add the VM::DEFAULT flag");
}
- // at this stage, only non-technique flags are asserted to be set
+ // at this stage, only settings technique flags are asserted to be set
if (
flags.test(NO_MEMO) ||
flags.test(HIGH_THRESHOLD) ||
@@ -8789,22 +8820,26 @@ struct VM {
// run every VM detection mechanism in the technique table
static u16 run_all(const flagset& flags, const bool shortcut = false) {
u16 points = 0;
+
const bool memo_enabled = core::is_disabled(flags, NO_MEMO);
const u16 threshold_points = (core::is_enabled(flags, HIGH_THRESHOLD) ? high_threshold_score : 200);
- // for main technique table
+ // loop through technique table, where all the techniques are stored
for (const auto& tmp : technique_table) {
const enum_flags technique_macro = tmp.first;
- const technique tuple = tmp.second;
+ const technique technique_data = tmp.second;
- // check if it's disabled
+ // check if the technique is disabled
if (core::is_disabled(flags, technique_macro)) {
continue;
}
// check if it's spoofable, and whether it's enabled
- if (tuple.spoofable && core::is_disabled(flags, SPOOFABLE)) {
+ if (
+ technique_data.is_spoofable &&
+ core::is_disabled(flags, SPOOFABLE)
+ ) {
continue;
}
@@ -8820,25 +8855,27 @@ struct VM {
}
// run the technique
- const bool result = tuple.run();
+ const bool result = technique_data.run();
// accumulate the points if technique detected a VM
if (result) {
- points += tuple.points;
- }
+ points += technique_data.points;
- /**
- * for things like VM::detect() and VM::percentage(),
- * a score of 200+ is guaranteed to be a VM, so
- * there's no point in running the rest of the techniques
- */
+ // this is specific to VM::detected_count() which returns
+ // the number of techniques that returned a positive
+ detected_count_num++;
+ }
+
+ // for things like VM::detect() and VM::percentage(),
+ // a score of 200+ is guaranteed to be a VM, so
+ // there's no point in running the rest of the techniques
if (shortcut && points >= threshold_points) {
return points;
}
// store the current technique result to the cache
if (memo_enabled) {
- memo::cache_store(technique_macro, result, tuple.points);
+ memo::cache_store(technique_macro, result, technique_data.points);
}
}
@@ -8859,13 +8896,20 @@ struct VM {
* basically what this entire template fuckery does is manage the
* variadic arguments being given through the arg_handler function,
* which could either be a std::bitset, a uint8_t, or a combination
- * of both of them. Thisz will handle both argument types and implement
+ * of both of them. This will handle both argument types and implement
* them depending on what their types are. If it's a std::bitset,
- * do the |= operation. If it's a uint8_t, simply .set() that into
- * the flag_collector bitset. That's the gist of it.
+ * do the |= operation on flag_collector. If it's a uint8_t, simply
+ * .set() that into the flag_collector. That's the gist of it.
*
* Also I won't even deny, the majority of this section was 90% generated
* by chatgpt. Can't be arsed with this C++ templatisation shit.
+ * Like is it really my fault that I have a hard time understanging C++'s
+ * god awful metaprogramming designs? And don't even get me started on SNIFAE.
+ *
+ * You don't need an IQ of 3 digits to realise how dogshit this language
+ * is, when you end up in situations where there's a few correct solutions
+ * to a problem, but with a billion ways you can do the same thing but in
+ * the "wrong" way. I genuinely can't wait for Carbon to come out.
*/
private:
static flagset flag_collector;
@@ -8983,18 +9027,23 @@ struct VM {
#endif
public:
- // Function template to test variadic arguments
+ // fetch the flags, could be an enum value OR a std::bitset.
+ // This will then generate a different std::bitset as the
+ // return value by enabling the bits based on the argument.
template
static flagset arg_handler(Args&&... args) {
- if VMAWARE_CONSTEXPR(is_empty()) {
+ if VMAWARE_CONSTEXPR (is_empty()) {
return DEFAULT;
}
flag_collector.reset();
global_flags.reset();
+ // set the bits in the flag, can take in
+ // either an enum value or a std::bitset
handleArgs(std::forward(args)...);
+ // handle edgecases
core::flag_sanitizer(flag_collector);
global_flags = flag_collector;
@@ -9007,14 +9056,15 @@ struct VM {
static flagset disabled_arg_handler(Args&&... args) {
flag_collector.reset();
- if VMAWARE_CONSTEXPR(is_empty()) {
- throw std::invalid_argument("VM::DISABLE must contain a flag");
+ if VMAWARE_CONSTEXPR (is_empty()) {
+ throw std::invalid_argument("VM::DISABLE() must contain a flag");
}
handle_disabled_args(std::forward(args)...);
- if (core::is_non_technique_set(flag_collector)) {
- throw std::invalid_argument("VM::DISABLE must not contain a non-technique flag, they are disabled by default anyway");
+ // check if a settings flag is set, which is not valid
+ if (core::is_setting_flag_set(flag_collector)) {
+ throw std::invalid_argument("VM::DISABLE() must not contain a settings flag, they are disabled by default anyway");
}
return flag_collector;
@@ -9051,7 +9101,7 @@ struct VM {
throw_error("Flag argument must be a valid");
}
- // check if the bit is a non-technique flag, which shouldn't be allowed
+ // check if the bit is a settings flag, which shouldn't be allowed
if (
(flag_bit == NO_MEMO) ||
(flag_bit == HIGH_THRESHOLD) ||
@@ -9081,6 +9131,10 @@ struct VM {
const core::technique& pair = it->second;
const bool result = pair.run();
+ if (result) {
+ detected_count_num++;
+ }
+
#ifdef __VMAWARE_DEBUG__
total_points += pair.points;
#endif
@@ -9102,15 +9156,17 @@ struct VM {
[[nodiscard]] static std::string brand(Args ...args) {
flagset flags = core::arg_handler(args...);
+ // is the multiple setting flag enabled? (meaning multiple
+ // brand strings will be outputted if there's a conflict)
const bool is_multiple = core::is_enabled(flags, MULTIPLE);
- // are all the techiques already run? if not, run all of them to get the necessary info to fetch the brand
+ // are all the techiques already run? if not, run them
+ // to fetch the necessary info to determine the brand
if (!memo::all_present() || core::is_enabled(flags, NO_MEMO)) {
- u16 tmp = core::run_all(flags);
- UNUSED(tmp);
+ core::run_all(flags);
}
- // check if it's already cached and return that instead
+ // check if the result is already cached and return that instead
if (core::is_disabled(flags, NO_MEMO)) {
if (is_multiple) {
if (memo::multi_brand::is_cached()) {
@@ -9125,7 +9181,8 @@ struct VM {
}
}
- // goofy ass C++11 and C++14 linker error workaround
+ // goofy ass C++11 and C++14 linker error workaround,
+ // and yes, this does look indeed stupid.
#if (CPP <= 14)
constexpr const char* TMP_QEMU = "QEMU";
constexpr const char* TMP_KVM = "KVM";
@@ -9168,27 +9225,42 @@ struct VM {
constexpr const char* TMP_HYPERV_ARTIFACT = HYPERV_ARTIFACT;
#endif
+ // this is where all the RELEVANT brands are stored.
+ // The ones with no points will be filtered out.
std::map brands;
+ // add the relevant brands with at least 1 point
for (const auto &element : core::brand_scoreboard) {
if (element.second > 0) {
brands.insert(std::make_pair(element.first, element.second));
}
}
- // if no brand had a single point, return "Unknown"
+ // if all brands had a point of 0, return
+ // "Unknown" (no relevant brands were found)
if (brands.empty()) {
return "Unknown";
}
+ // if there's only a single brand, return it.
+ // This will skip the rest of the function
+ // where it will process and merge certain
+ // brands
if (brands.size() == 1) {
return brands.begin()->first;
- } else if (brands.size() > 1) {
+ }
+
+ // remove Hyper-V artifacts if found with other
+ // brands, because that's not a VM. It's added
+ // only for the sake of information cuz of the
+ // fucky wucky Hyper-V problem (see Hyper-X)
+ if (brands.size() > 1) {
if (brands.find(TMP_HYPERV_ARTIFACT) != brands.end()) {
brands.erase(TMP_HYPERV_ARTIFACT);
}
}
+ // merge 2 brands, and make a single brand out of it.
auto merger = [&](const char* a, const char* b, const char* result) -> void {
if (
(brands.count(a) > 0) &&
@@ -9200,6 +9272,7 @@ struct VM {
}
};
+ // same as above, but for 3
auto triple_merger = [&](const char* a, const char* b, const char* c, const char* result) -> void {
if (
(brands.count(a) > 0) &&
@@ -9213,6 +9286,8 @@ struct VM {
}
};
+ // some edgecase handling for Hyper-V and VirtualPC since
+ // they're very similar, and they're both from Microsoft (ew)
if ((brands.count(TMP_HYPERV) > brands.count(TMP_VPC))) {
brands.erase(TMP_VPC);
} else if (brands.count(TMP_HYPERV) < brands.count(TMP_VPC)) {
@@ -9224,9 +9299,19 @@ struct VM {
merger(TMP_VPC, TMP_HYPERV, TMP_HYPERV_VPC);
}
- merger(TMP_HYPERV, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT);
- merger(TMP_VPC, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT);
- merger(TMP_HYPERV_VPC, TMP_HYPERV_ARTIFACT, TMP_HYPERV_ARTIFACT);
+
+ // this is the section where brand post-processing will be done.
+ // The reason why this part is necessary is because it will
+ // output a more accurate picture on the VM brand. For example,
+ // Azure's cloud is based on Hyper-V, but Hyper-V may have
+ // a higher score due to the prevalence of it in a practical
+ // setting, which will put Azure to the side. This is stupid
+ // because there should be an indication that Azure is involved
+ // since it's a better idea to let the end-user know that the
+ // brand is "Azure Hyper-V" instead of just "Hyper-V". So what
+ // this section does is "merge" the brands together to form
+ // a more accurate idea of the brand(s) involved.
+
merger(TMP_AZURE, TMP_HYPERV, TMP_AZURE);
merger(TMP_AZURE, TMP_VPC, TMP_AZURE);
@@ -9252,8 +9337,11 @@ struct VM {
merger(TMP_VMWARE, TMP_GSX, TMP_GSX);
merger(TMP_VMWARE, TMP_WORKSTATION, TMP_WORKSTATION);
+ // the brand element, which stores the NAME (const char*) and the SCORE (u8)
using brand_element_t = std::pair;
+ // sort the "brands" map so that the brands with the
+ // highest score appears first in descending order
auto sorter = [&]() -> std::vector {
std::vector vec(brands.begin(), brands.end());
@@ -9270,11 +9358,15 @@ struct VM {
std::vector vec = sorter();
std::string ret_str = "Unknown";
+ // if the multiple setting flag is NOT set, return the
+ // brand with the highest score. Else, return a std::string
+ // of the brand message (i.e. "VirtualBox or VMware").
+ // See VM::MULTIPLE flag in docs for more information.
if (!is_multiple) {
ret_str = vec.front().first;
} else {
std::stringstream ss;
- u8 i = 1;
+ std::size_t i = 1;
ss << vec.front().first;
for (; i < vec.size(); i++) {
@@ -9284,6 +9376,7 @@ struct VM {
ret_str = ss.str();
}
+ // cache the result if memoization is enabled
if (core::is_disabled(flags, NO_MEMO)) {
if (is_multiple) {
core_debug("VM::brand(): cached multiple brand string");
@@ -9294,7 +9387,7 @@ struct VM {
}
}
- // this gets annoying really fast
+ // debug stuff to see the brand scoreboard, ignore this
#ifdef __VMAWARE_DEBUG__
for (const auto p : brands) {
core_debug("scoreboard: ", (int)p.second, " : ", p.first);
@@ -9313,23 +9406,26 @@ struct VM {
*/
template
static bool detect(Args ...args) {
+ // fetch all the flags in a std::bitset
flagset flags = core::arg_handler(args...);
+ // run all the techniques based on the
+ // flags above, and get a total score
const u16 points = core::run_all(flags, SHORTCUT);
#if (CPP >= 23)
[[assume(points < maximum_points)]];
#endif
- bool result = false;
+ u16 threshold = 150;
+ // if high threshold is set, the points
+ // will be 300. If not, leave it as 150.
if (core::is_enabled(flags, HIGH_THRESHOLD)) {
- result = (points >= high_threshold_score);
- } else {
- result = (points >= 150);
+ threshold = high_threshold_score;
}
- return result;
+ return (points >= threshold);
}
@@ -9341,21 +9437,29 @@ struct VM {
*/
template
static u8 percentage(Args ...args) {
+ // fetch all the flags in a std::bitset
const flagset flags = core::arg_handler(args...);
+ // run all the techniques based on the
+ // flags above, and get a total score
const u16 points = core::run_all(flags, SHORTCUT);
- u8 percent = 0;
#if (CPP >= 23)
[[assume(points < maximum_points)]];
#endif
+ u8 percent = 0;
u16 threshold = 150;
+ // set to 300 if high threshold is enabled
if (core::is_enabled(flags, HIGH_THRESHOLD)) {
threshold = high_threshold_score;
}
+ // the percentage will be set to 99%, because a score
+ // of 100 is not entirely robust. 150 is more robust
+ // in my opinion, which is why you need a score of
+ // above 150 to get to 100%
if (points >= threshold) {
percent = 100;
} else if (points >= 100) {
@@ -9382,6 +9486,7 @@ struct VM {
, const std::source_location& loc = std::source_location::current()
#endif
) {
+ // lambda to throw the error
auto throw_error = [&](const char* text) -> void {
std::stringstream ss;
#if (CPP >= 20 && !CLANG)
@@ -9399,11 +9504,13 @@ struct VM {
[[assume(percent > 0 && percent <= 100)]];
#endif
+ // generate the custom technique struct
core::custom_technique query{
percent,
detection_func
};
+ // push it to the custome_table vector
core::custom_table.emplace_back(query);
}
@@ -9416,6 +9523,8 @@ struct VM {
*/
template
static flagset DISABLE(Args ...args) {
+ // basically core::arg_handler but in reverse,
+ // it'll clear the bits of the provided flags
flagset flags = core::disabled_arg_handler(args...);
flags.flip();
@@ -9431,7 +9540,7 @@ struct VM {
/**
* @brief This will convert the technique flag into a string, which will correspond to the technique name
* @param single technique flag in VM structure
- * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️
+ * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️
*/
[[nodiscard]] static std::string flag_to_string(const enum_flags flag) {
switch (flag) {
@@ -9545,9 +9654,9 @@ struct VM {
/**
- * @brief return a vector of detected brand strings (DEVELOPMENT FUNCTION, NOT MEANT FOR PUBLIC USE)
+ * @brief return a vector of detected brand strings
* @param any flag combination in VM structure or nothing
- * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️
+ * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️
*/
template
static std::map brand_map(Args ...args) {
@@ -9555,8 +9664,7 @@ struct VM {
// are all the techiques already run? if not, run all of them to get the necessary info to fetch the brand
if (!memo::all_present() || core::is_enabled(flags, NO_MEMO)) {
- u16 tmp = core::run_all(flags);
- UNUSED(tmp);
+ core::run_all(flags);
}
return core::brand_scoreboard;
@@ -9567,16 +9675,17 @@ struct VM {
* @brief Change the certainty score of a technique
* @param technique flag, then the new percentage score to overwite
* @return void
- * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE ⚠️
+ * @warning ⚠️ FOR DEVELOPMENT USAGE ONLY, NOT MEANT FOR PUBLIC USE FOR NOW ⚠️
*/
static void modify_score(
const enum_flags flag,
- const std::uint8_t percent
+ const u8 percent
// clang doesn't support std::source_location for some reason
#if (CPP >= 20 && !CLANG)
, const std::source_location& loc = std::source_location::current()
#endif
) {
+ // lambda to throw the error
auto throw_error = [&](const char* text) -> void {
std::stringstream ss;
#if (CPP >= 20 && !CLANG)
@@ -9594,19 +9703,196 @@ struct VM {
[[assume(percent <= 100)]];
#endif
+ // check if the flag provided is a setting flag, which isn't valid.
if (static_cast(flag) >= technique_end) {
throw_error("The flag is not a technique flag");
}
+ // replica type alias of the technique table
using table_t = std::map;
auto modify = [](table_t &table, const enum_flags flag, const u8 percent) -> void {
core::technique &tmp = table.at(flag);
- table[flag] = { percent, tmp.run, tmp.spoofable };
+ table[flag] = { percent, tmp.run, tmp.is_spoofable };
};
modify(const_cast(core::technique_table), flag, percent);
}
+
+
+ /**
+ * @brief Fetch the total number of detected techniques
+ * @param any flag combination in VM structure or nothing
+ * @return std::uint8_t
+ */
+ template
+ static u8 detected_count(Args ...args) {
+ flagset flags = core::arg_handler(args...);
+
+ // run all the techniques, which will set the detected_count variable
+ core::run_all(flags);
+
+ return detected_count_num;
+ }
+
+
+ /**
+ * @brief Fetch the total number of detected techniques
+ * @param any flag combination in VM structure or nothing
+ * @return std::uint8_t
+ */
+ template
+ static std::string type(Args ...args) {
+ flagset flags = core::arg_handler(args...);
+
+ const std::string brand_str = brand(flags);
+
+ // if multiple brands were found, return unknown
+ if (util::find(brand_str, " or ")) {
+ return "Unknown";
+ }
+
+ const std::map type_table {
+ // type 1
+ { XEN, "Hypervisor (type 1)" },
+ { VMWARE_ESX, "Hypervisor (type 1)" },
+ { ACRN, "Hypervisor (type 1)" },
+ { QNX, "Hypervisor (type 1)" },
+ { HYPERV, "Hypervisor (type 1)" },
+ { AZURE_HYPERV, "Hypervisor (type 1)" },
+ { NANOVISOR, "Hypervisor (type 1)" },
+ { KVM, "Hypervisor (type 1)" },
+ { BHYVE, "Hypervisor (type 1)" },
+ { KVM_HYPERV, "Hypervisor (type 1)" },
+ { QEMU_KVM_HYPERV, "Hypervisor (type 1)" },
+ { QEMU_KVM, "Hypervisor (type 1)" },
+ { INTEL_HAXM, "Hypervisor (type 1)" },
+ { INTEL_KGT, "Hypervisor (type 1)" },
+ { SIMPLEVISOR, "Hypervisor (type 1)" },
+ { GCE, "Hypervisor (type 1)" },
+ { OPENSTACK, "Hypervisor (type 1)" },
+ { KUBEVIRT, "Hypervisor (type 1)" },
+ { POWERVM, "Hypervisor (type 1)" },
+ { AWS_NITRO, "Hypervisor (type 1)" },
+
+ // type 2
+ { VBOX, "Hypervisor (type 2)" },
+ { VMWARE, "Hypervisor (type 2)" },
+ { VMWARE_EXPRESS, "Hypervisor (type 2)" },
+ { VMWARE_GSX, "Hypervisor (type 2)" },
+ { VMWARE_WORKSTATION, "Hypervisor (type 2)" },
+ { VMWARE_FUSION, "Hypervisor (type 2)" },
+ { PARALLELS, "Hypervisor (type 2)" },
+ { VPC, "Hypervisor (type 2)" },
+ { NVMM, "Hypervisor (type 2)" },
+ { BSD_VMM, "Hypervisor (type 2)" },
+
+ // sandbox
+ { CUCKOO, "Sandbox" },
+ { SANDBOXIE, "Sandbox" },
+ { HYBRID, "Sandbox" },
+ { CWSANDBOX, "Sandbox" },
+ { JOEBOX, "Sandbox" },
+ { ANUBIS, "Sandbox" },
+ { COMODO, "Sandbox" },
+ { THREATEXPERT, "Sandbox" },
+ { ANYRUN, "Sandbox"},
+
+ // misc
+ { BOCHS, "Emulator" },
+ { BLUESTACKS, "Emulator" },
+ { MSXTA, "Emulator" },
+ { QEMU, "Emulator/Hypervisor (type 2)" },
+ { JAILHOUSE, "Partitioning Hypervisor" },
+ { UNISYS, "Partitioning Hypervisor" },
+ { DOCKER, "Container" },
+ { PODMAN, "Container" },
+ { OPENVZ, "Container" },
+ { HYPERV_VPC, "Hypervisor (either type 1 or 2)" },
+ { LMHS, "Hypervisor (unknown type)" },
+ { WINE, "Compatibility layer" },
+ { APPLE_VZ, "Unknown" },
+ { HYPERV_ARTIFACT, "Unknown" },
+ { UML, "Paravirtualised/Hypervisor (type 2)" },
+ { WSL, "Hybrid Hyper-V (type 1 and 2)" }, // debatable tbh
+ { APPLE_ROSETTA, "Binary Translation Layer/Emulator" },
+ };
+
+ auto it = type_table.find(brand_str.c_str());
+
+ if (it != type_table.end()) {
+ return it->second;
+ }
+
+ return "Unknown";
+ }
+
+
+ /**
+ * @brief Fetch the conclusion message based on the brand and percentage
+ * @param any flag combination in VM structure or nothing
+ * @return std::string
+ */
+ template
+ static std::string conclusion(Args ...args) {
+ flagset flags = core::arg_handler(args...);
+
+ const std::string brand_tmp = brand(flags);
+ const u8 percent_tmp = percentage(flags);
+
+ constexpr const char* baremetal = "Running in baremetal";
+ constexpr const char* very_unlikely = "Very unlikely a VM";
+ constexpr const char* unlikely = "Unlikely a VM";
+
+ std::string potentially = "Potentially a VM";
+ std::string might = "Might be a VM";
+ std::string likely = "Likely a VM";
+ std::string very_likely = "Very likely a VM";
+ std::string inside_vm = "Running inside a VM";
+
+ if (brand_tmp != "Unknown") {
+ potentially = "Potentially a " + brand_tmp + " VM";
+ might = "Might be a " + brand_tmp + " VM";
+ likely = "Likely a " + brand_tmp + " VM";
+ very_likely = "Very likely a " + brand_tmp + " VM";
+ inside_vm = "Running inside a " + brand_tmp + " VM";
+ }
+
+ if (percent_tmp == 0) { return baremetal; }
+ else if (percent_tmp <= 20) { return very_unlikely; }
+ else if (percent_tmp <= 35) { return unlikely; }
+ else if (percent_tmp < 50) { return potentially; }
+ else if (percent_tmp <= 62) { return might; }
+ else if (percent_tmp <= 75) { return likely; }
+ else if (percent_tmp < 100) { return very_likely; }
+ else { return inside_vm; }
+ }
+
+
+ struct vmaware {
+ bool is_vm;
+ u8 percentage;
+ u8 detected_count;
+ u8 technique_count;
+ std::string brand;
+ std::string type;
+ std::string conclusion;
+
+ vmaware() = default;
+
+ template
+ vmaware(Args ...args) {
+ flagset flags = core::arg_handler(args...);
+
+ is_vm = VM::detect(flags);
+ percentage = VM::percentage(flags);
+ detected_count = VM::detected_count(flags);
+ technique_count = VM::technique_count;
+ brand = VM::brand(flags);
+ type = VM::type(flags);
+ conclusion = VM::conclusion(flags);
+ }
+ };
};
MSVC_ENABLE_WARNING(ASSIGNMENT_OPERATOR NO_INLINE_FUNC SPECTRE)
@@ -9691,12 +9977,22 @@ bool VM::memo::hyperv::is_stored = false;
VM::u16 VM::total_points = 0;
#endif
-// not even sure how to explain honestly, just pretend these don't exist idfk
+// these are basically the base values for the core::arg_handler function.
+// It's like a bucket that will collect all the bits enabled. If for example
+// VM::detect(VM::HIGH_THRESHOLD) is passed, the HIGH_THRESHOLD bit will be
+// collected in this flagset (std::bitset) variable, and eventually be the
+// return value for actual end-user functions like VM::detect() to rely
+// and work on. VM::global_flags is just a copy of the flags but visible
+// globally throughout the whole VM struct, as the name implies.
VM::flagset VM::core::flag_collector;
VM::flagset VM::global_flags;
+
+VM::u8 VM::detected_count_num = 0;
+
+
// default flags
-VM::flagset VM::DEFAULT = []() -> flagset {
+VM::flagset VM::DEFAULT = []() noexcept -> flagset {
flagset tmp;
// set all bits to 1
@@ -9707,7 +10003,7 @@ VM::flagset VM::DEFAULT = []() -> flagset {
tmp.flip(RDTSC);
tmp.flip(RDTSC_VMEXIT);
- // disable all the non-technique flags
+ // disable all the settings flags
tmp.flip(NO_MEMO);
tmp.flip(HIGH_THRESHOLD);
tmp.flip(SPOOFABLE);
@@ -9718,13 +10014,13 @@ VM::flagset VM::DEFAULT = []() -> flagset {
// flag to enable every technique
-VM::flagset VM::ALL = []() -> flagset {
+VM::flagset VM::ALL = []() noexcept -> flagset {
flagset tmp;
// set all bits to 1
tmp.set();
- // disable all the non-technique flags (except SPOOFABLE)
+ // disable all the settings technique flags (except SPOOFABLE)
tmp.flip(NO_MEMO);
tmp.flip(HIGH_THRESHOLD);
tmp.flip(MULTIPLE);
@@ -9769,10 +10065,9 @@ std::vector VM::core::custom_table = {
};
-
// the 0~100 points are debatable, but I think it's fine how it is. Feel free to disagree.
const std::map VM::core::technique_table = {
- // FORMAT: VM:: = { certainty%, function pointer, is spoofable? }
+ // FORMAT: { VM::, { certainty%, function pointer, is spoofable? } },
{ VM::VMID, { 100, VM::vmid, false } },
{ VM::CPU_BRAND, { 50, VM::cpu_brand, false } },
@@ -9846,7 +10141,7 @@ const std::map VM::core::technique_table =
{ VM::MUTEX, { 85, VM::mutex, false } },
{ VM::UPTIME, { 10, VM::uptime, true } },
{ VM::ODD_CPU_THREADS, { 80, VM::odd_cpu_threads, false } },
- { VM::INTEL_THREAD_MISMATCH, { 85, VM::intel_thread_mismatch, false } },
+ { VM::INTEL_THREAD_MISMATCH, { 60, VM::intel_thread_mismatch, false } },
{ VM::XEON_THREAD_MISMATCH, { 85, VM::xeon_thread_mismatch, false } },
{ VM::NETTITUDE_VM_MEMORY, { 75, VM::nettitude_vm_memory, false } },
{ VM::CPUID_BITSET, { 20, VM::cpuid_bitset, false } },
@@ -9878,4 +10173,4 @@ const std::map VM::core::technique_table =
{ VM::WSL_PROC, { 30, VM::wsl_proc_subdir, false } },
{ VM::ANYRUN_DRIVER, { 65, VM::anyrun_driver, false } },
{ VM::ANYRUN_DIRECTORY, { 35, VM::anyrun_directory, false } }
-};
\ No newline at end of file
+};