Skip to content

Commit a23b17e

Browse files
author
Requiem
committed
feat: added new PCI enumeration routines
1 parent 621a1c1 commit a23b17e

2 files changed

Lines changed: 147 additions & 104 deletions

File tree

src/cli.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ static std::string vm_description(const std::string& vm_brand) {
464464
{ brands::AZURE_HYPERV, "Azure Hyper-V is Microsoft's cloud-optimized hypervisor variant powering Azure VMs. Implements Azure-specific virtual devices like NVMe Accelerated Networking and vTPMs. Supports nested virtualization for running Hyper-V/containers within Azure VMs, enabling cloud-based CI/CD pipelines and dev/test environments." },
465465
{ brands::NANOVISOR, "NanoVisor is a Hyper-V modification serving as the host OS of Xbox's devices: the Xbox System Software. It contains 2 partitions: the \"Exclusive\" partition is a custom VM for games, while the other partition, called the \"Shared\" partition is a custom VM for running multiple apps including the OS itself. The OS was based on Windows 8 Core at the Xbox One launch in 2013." },
466466
{ brands::SIMPLEVISOR, "SimpleVisor is a minimalist Intel VT-x hypervisor by Alex Ionescu for Windows/Linux research. Demonstrates EPT-based memory isolation and hypercall handling. Used to study VM escapes and hypervisor rootkits, with hooks for intercepting CR3 changes and MSR accesses." },
467-
{ brands::HYPERV_ARTIFACT, "VMAware detected Hyper-V operating as a Type 1 hypervisor, not as a guest virtual machine. Although your hardware/firmware signatures match Microsoft's Hyper-V architecture, we determined that you're running on baremetal, with the help of our \"Hyper-X\" mechanism that differentiates between the root partition (host OS) and guest VM environments. This prevents false positives, as Windows sometimes runs under Hyper-V (type 1) hypervisor." },
467+
{ brands::HYPERV_ARTIFACT, "VMAware detected Hyper-V operating as a type 1 hypervisor, not as a guest virtual machine. Although your hardware/firmware signatures match Microsoft's Hyper-V architecture, we determined that you're running on baremetal. This prevents false positives, as Windows sometimes runs under Hyper-V (type 1) hypervisor." },
468468
{ brands::UML, "User-Mode Linux (UML) allows running Linux kernels as user-space processes using ptrace-based virtualization. Primarily used for kernel debugging and network namespace testing. Offers lightweight isolation without hardware acceleration, but requires host/guest kernel version matching for stable operation." },
469469
{ brands::POWERVM, "IBM PowerVM is a type 1 hypervisor for POWER9/10 systems, supporting Live Partition Mobility and Shared Processor Pools. Implements VIOS (Virtual I/O Server) for storage/networking virtualization, enabling concurrent AIX, IBM i, and Linux workloads with RAS features like predictive failure analysis." },
470470
{ brands::GCE, "Google Compute Engine (GCE) utilizes KVM-based virtualization with custom Titanium security chips for hardware root of trust. Features live migration during host maintenance and shielded VMs with UEFI secure boot. Underpins Google Cloud's Confidential Computing offering using AMD SEV-SNP memory encryption." },

src/vmaware.hpp

Lines changed: 146 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -6070,7 +6070,6 @@ struct VM {
60706070
#endif
60716071
}
60726072

6073-
60746073
/**
60756074
* @brief Check for PCI vendor and device IDs that are VM-specific
60766075
* @link https://www.pcilookup.com/?ven=&dev=&action=submit
@@ -6153,9 +6152,62 @@ struct VM {
61536152

61546153
std::unordered_set<unsigned long long> seen;
61556154

6156-
// Lambda #1: Process the hardware ID on an instance key,
6157-
// extract every (VID, DID) pair, and push into devices
6158-
auto process_hardware_id = [&](HKEY h_inst, root_type root_type) {
6155+
auto add_device = [&](u16 vid, u32 did) noexcept {
6156+
const unsigned long long key = (static_cast<unsigned long long>(vid) << 32) | static_cast<unsigned long long>(did);
6157+
if (seen.insert(key).second) {
6158+
devices.push_back({ vid, did });
6159+
}
6160+
};
6161+
6162+
auto scan_text_ids = [&](const wchar_t* text) noexcept {
6163+
if (!text) return;
6164+
6165+
// USB: VID_ and then PID_
6166+
const wchar_t* p = text;
6167+
while ((p = wcsstr(p, L"VID_"))) {
6168+
const wchar_t* v = p;
6169+
p += 4;
6170+
const wchar_t* d = wcsstr(v + 4, L"PID_");
6171+
if (d && (d - v) < 64) {
6172+
unsigned long parsed_v = 0, parsed_d = 0;
6173+
size_t c_v = 0, c_d = 0;
6174+
if (parse_hex(v + 4, 4, SIZE_MAX, parsed_v, c_v) &&
6175+
parse_hex(d + 4, 8, SIZE_MAX, parsed_d, c_d)) {
6176+
add_device(static_cast<u16>(parsed_v & 0xFFFFu), static_cast<u32>(parsed_d));
6177+
}
6178+
}
6179+
}
6180+
6181+
// PCI or HDAUDIO = VEN_ and then DEV_ after it
6182+
p = text;
6183+
while ((p = wcsstr(p, L"VEN_"))) {
6184+
const wchar_t* v = p;
6185+
p += 4;
6186+
const wchar_t* d = wcsstr(v + 4, L"DEV_");
6187+
if (d && (d - v) < 64) {
6188+
unsigned long parsed_v = 0;
6189+
size_t c_v = 0;
6190+
if (parse_hex(v + 4, 4, SIZE_MAX, parsed_v, c_v)) {
6191+
const wchar_t* dev_start = const_cast<wchar_t*>(d + 4);
6192+
const wchar_t* amp_after_dev = wcschr(dev_start, L'&');
6193+
const size_t dev_len = amp_after_dev ? static_cast<size_t>(amp_after_dev - dev_start) : wcslen(dev_start);
6194+
6195+
// for HDAUDIO expect 4 digits and for PCI allow up to 8
6196+
if (dev_len > 0 && dev_len <= 8) {
6197+
unsigned long parsed_d = 0;
6198+
size_t c_d = 0;
6199+
// parse exactly devLen digits (fail if any char is non-hex)
6200+
if (parse_hex(dev_start, 8, dev_len, parsed_d, c_d) && c_d == dev_len) {
6201+
add_device(static_cast<u16>(parsed_v & 0xFFFFu), static_cast<u32>(parsed_d));
6202+
}
6203+
}
6204+
}
6205+
}
6206+
}
6207+
};
6208+
6209+
// process the hardware ID on an instance key
6210+
auto process_hardware_id_reg = [&](HKEY h_inst) noexcept {
61596211
// most HardwareIDs fit within 512 bytes
61606212
static thread_local std::vector<wchar_t> buf;
61616213
if (buf.empty()) buf.resize(512);
@@ -6175,7 +6227,6 @@ struct VM {
61756227

61766228
if (rv == ERROR_MORE_DATA) {
61776229
if (cb_data > MAX_MULTI_SZ) {
6178-
debug("PCI_DEVICES: HardwareID size too large: ", cb_data);
61796230
return;
61806231
}
61816232

@@ -6205,94 +6256,19 @@ struct VM {
62056256
else buf.back() = L'\0';
62066257

62076258
for (wchar_t* p = buf.data(); *p; p += wcslen(p) + 1) {
6208-
wchar_t* s = p;
6209-
wchar_t* v = nullptr;
6210-
wchar_t* d = nullptr;
6211-
u16 vid = 0;
6212-
u32 did = 0;
6213-
bool ok = false;
6214-
6215-
if (root_type == RT_USB) {
6216-
// USB: VID_ and then PID_
6217-
v = wcsstr(s, L"VID_");
6218-
if (v) d = wcsstr(v + 4, L"PID_");
6219-
if (v && d) {
6220-
unsigned long parsed_v = 0, parsed_d = 0;
6221-
size_t c_v = 0, c_d = 0;
6222-
// VID_ usually 4 hex digits, PID_ usually 4
6223-
if (parse_hex(v + 4, 4, SIZE_MAX, parsed_v, c_v) &&
6224-
parse_hex(d + 4, 8, SIZE_MAX, parsed_d, c_d)) {
6225-
vid = static_cast<u16>(parsed_v & 0xFFFFu);
6226-
did = static_cast<u32>(parsed_d);
6227-
ok = true;
6228-
}
6229-
}
6230-
}
6231-
else {
6232-
// PCI or HDAUDIO = VEN_ and then DEV_ after it
6233-
v = wcsstr(s, L"VEN_");
6234-
if (v) d = wcsstr(v + 4, L"DEV_");
6235-
if (v && d) {
6236-
unsigned long parsed_v = 0;
6237-
size_t c_v = 0;
6238-
if (!parse_hex(v + 4, 4, SIZE_MAX, parsed_v, c_v)) {
6239-
continue;
6240-
}
6241-
vid = static_cast<u16>(parsed_v & 0xFFFFu);
6242-
6243-
wchar_t* dev_start = d + 4;
6244-
wchar_t* amp_after_dev = wcschr(dev_start, L'&');
6245-
const size_t dev_len = amp_after_dev ? static_cast<size_t>(amp_after_dev - dev_start) : wcslen(dev_start);
6246-
6247-
// for HDAUDIO expect 4 digits and for PCI allow up to 8
6248-
const size_t max_digits = (root_type == RT_HDAUDIO) ? 4 : 8;
6249-
if (dev_len == 0 || dev_len > max_digits) {
6250-
// if the token is longer than maxDigits, we cap parsing to maxDigits but
6251-
// require that the parsed digit count equals devLen
6252-
if (dev_len > max_digits) continue;
6253-
}
6254-
6255-
unsigned long parsed_d = 0;
6256-
size_t c_d = 0;
6257-
// parse exactly devLen digits (fail if any char is non-hex)
6258-
if (!parse_hex(dev_start, max_digits, dev_len, parsed_d, c_d)) {
6259-
continue;
6260-
}
6261-
// require we consumed all characters in device token (like std::stoul on the substring)
6262-
if (c_d != dev_len) continue;
6263-
6264-
// overflow checks
6265-
if (root_type == RT_HDAUDIO) {
6266-
if (parsed_d > 0xFFFF) continue;
6267-
did = static_cast<u32>(parsed_d & 0xFFFFu);
6268-
}
6269-
else {
6270-
// PCI device id may be up to 32-bit
6271-
did = static_cast<u32>(parsed_d);
6272-
}
6273-
ok = true;
6274-
}
6275-
}
6276-
6277-
if (ok) {
6278-
const unsigned long long key = (static_cast<unsigned long long>(vid) << 32) | static_cast<unsigned long long>(did);
6279-
if (seen.insert(key).second) {
6280-
devices.push_back({ vid, did });
6281-
}
6282-
}
6259+
scan_text_ids(p);
62836260
}
62846261
};
62856262

6286-
// Lambda #2: all instance subkeys under a given device key,
6287-
// and for each instance, open it and call processHardwareID()
6288-
auto enum_instances = [&](HKEY h_dev, root_type root_type) {
6263+
// all instance subkeys under a given device key
6264+
auto enum_instances = [&](HKEY h_dev) noexcept {
62896265
wchar_t inst_name[256];
62906266

62916267
for (DWORD j = 0;; ++j) {
62926268
// reset size for each iteration as RegEnumKeyExW modifies it
62936269
DWORD cb_inst = _countof(inst_name);
62946270

6295-
LONG st2 = RegEnumKeyExW(
6271+
const LONG st2 = RegEnumKeyExW(
62966272
h_dev,
62976273
j,
62986274
inst_name,
@@ -6308,20 +6284,19 @@ struct VM {
63086284
HKEY h_inst = nullptr;
63096285
if (RegOpenKeyExW(h_dev, inst_name, 0, KEY_READ, &h_inst) != ERROR_SUCCESS) continue;
63106286

6311-
process_hardware_id(h_inst, root_type);
6287+
process_hardware_id_reg(h_inst);
63126288
RegCloseKey(h_inst);
63136289
}
63146290
};
63156291

6316-
// Lambda #3: all device subkeys under a given root key,
6317-
// open each device key, and call enumInstances()
6318-
auto enum_devices = [&](HKEY h_root, root_type root_type) {
6292+
// all device subkeys under a given root key
6293+
auto enum_devices = [&](HKEY h_root) noexcept {
63196294
wchar_t device_name[256];
63206295

63216296
for (DWORD i = 0;; ++i) {
63226297
DWORD cb_name = _countof(device_name);
63236298

6324-
LONG status = RegEnumKeyExW(
6299+
const LONG status = RegEnumKeyExW(
63256300
h_root,
63266301
i,
63276302
device_name,
@@ -6337,12 +6312,12 @@ struct VM {
63376312
HKEY h_dev = nullptr;
63386313
if (RegOpenKeyExW(h_root, device_name, 0, KEY_READ, &h_dev) != ERROR_SUCCESS) continue;
63396314

6340-
enum_instances(h_dev, root_type);
6315+
enum_instances(h_dev);
63416316
RegCloseKey(h_dev);
63426317
}
63436318
};
63446319

6345-
// for each rootPath we open the root key once, compute its RootType, then call enumDevices()
6320+
// for each rootPath we open the root key once
63466321
for (size_t rootIdx = 0; rootIdx < _countof(kRoots); ++rootIdx) {
63476322
const wchar_t* rootPath = kRoots[rootIdx];
63486323
HKEY hRoot = nullptr;
@@ -6356,20 +6331,88 @@ struct VM {
63566331
continue;
63576332
}
63586333

6359-
root_type rootType;
6360-
if (wcscmp(rootPath, L"SYSTEM\\CurrentControlSet\\Enum\\USB") == 0) {
6361-
rootType = RT_USB;
6362-
}
6363-
else if (wcscmp(rootPath, L"SYSTEM\\CurrentControlSet\\Enum\\HDAUDIO") == 0) {
6364-
rootType = RT_HDAUDIO;
6365-
}
6366-
else {
6367-
rootType = RT_PCI;
6368-
}
6369-
6370-
enum_devices(hRoot, rootType);
6334+
enum_devices(hRoot);
63716335
RegCloseKey(hRoot);
63726336
}
6337+
6338+
auto scan_evt_logs = [&]() noexcept {
6339+
static constexpr const wchar_t* q = L"<QueryList>"
6340+
L" <Query Id='0' Path='Microsoft-Windows-Kernel-PnP/Device Management'>"
6341+
L" <Select Path='Microsoft-Windows-Kernel-PnP/Device Management'>"
6342+
L" *[System[(Provider[@Name='Microsoft-Windows-Kernel-PnP'] and (EventID=1065 or EventID=1011 or EventID=1010 or EventID=1000 or EventID=1012 or EventID=1013 or EventID=1014))]]"
6343+
L" </Select>"
6344+
L" </Query>"
6345+
L" <Query Id='1' Path='Microsoft-Windows-Kernel-PnP/Driver Watchdog'>"
6346+
L" <Select Path='Microsoft-Windows-Kernel-PnP/Driver Watchdog'>"
6347+
L" *[System[(Provider[@Name='Microsoft-Windows-Kernel-PnP'] and (EventID=900 or EventID=901 or EventID=902 or EventID=903))]]"
6348+
L" </Select>"
6349+
L" </Query>"
6350+
L" <Query Id='2' Path='Microsoft-Windows-UserPnp/DeviceInstall'>"
6351+
L" <Select Path='Microsoft-Windows-UserPnp/DeviceInstall'>"
6352+
L" *[System[(Provider[@Name='Microsoft-Windows-UserPnp'] and (EventID=8000 or EventID=8001))]]"
6353+
L" </Select>"
6354+
L" </Query>"
6355+
L"</QueryList>";
6356+
6357+
const EVT_HANDLE hSub = EvtQuery(nullptr, nullptr, q, EvtQueryReverseDirection | EvtQueryTolerateQueryErrors);
6358+
if (!hSub) return;
6359+
6360+
EVT_HANDLE hEvents[1] = { nullptr };
6361+
DWORD returned = 0;
6362+
6363+
static thread_local std::vector<wchar_t> buf;
6364+
if (buf.empty()) buf.resize(8192);
6365+
6366+
while (true) {
6367+
returned = 0;
6368+
hEvents[0] = nullptr;
6369+
6370+
if (!EvtNext(hSub, 1, hEvents, 2000, 0, &returned)) {
6371+
const DWORD err = GetLastError();
6372+
if (err == ERROR_NO_MORE_ITEMS) break;
6373+
if (err == ERROR_TIMEOUT) continue;
6374+
break;
6375+
}
6376+
6377+
if (returned == 0) continue;
6378+
6379+
DWORD bufUsed = 0;
6380+
DWORD bufProps = 0;
6381+
6382+
if (!EvtRender(nullptr, hEvents[0], EvtRenderEventXml,
6383+
static_cast<DWORD>(buf.size() * sizeof(wchar_t)),
6384+
buf.data(), &bufUsed, &bufProps)) {
6385+
const DWORD err = GetLastError();
6386+
if (err == ERROR_INSUFFICIENT_BUFFER) {
6387+
size_t neededChars = (bufUsed + sizeof(wchar_t) - 1) / sizeof(wchar_t);
6388+
buf.resize(neededChars + 1);
6389+
6390+
if (!EvtRender(nullptr, hEvents[0], EvtRenderEventXml,
6391+
static_cast<DWORD>(buf.size() * sizeof(wchar_t)),
6392+
buf.data(), &bufUsed, &bufProps)) {
6393+
if (hEvents[0]) { EvtClose(hEvents[0]); hEvents[0] = nullptr; }
6394+
continue;
6395+
}
6396+
}
6397+
else {
6398+
if (hEvents[0]) { EvtClose(hEvents[0]); hEvents[0] = nullptr; }
6399+
continue;
6400+
}
6401+
}
6402+
6403+
const size_t charCount = bufUsed / sizeof(wchar_t);
6404+
if (charCount >= buf.size()) buf.resize(charCount + 1);
6405+
buf[charCount] = L'\0';
6406+
6407+
scan_text_ids(buf.data());
6408+
6409+
if (hEvents[0]) { EvtClose(hEvents[0]); hEvents[0] = nullptr; }
6410+
}
6411+
6412+
EvtClose(hSub);
6413+
};
6414+
6415+
scan_evt_logs();
63736416
#endif
63746417

63756418
for (auto& d : devices) {

0 commit comments

Comments
 (0)