qemu-spoof: seed-driven per-VM hardware-identity anti-detection for pve-qemu

This commit is contained in:
2026-06-11 17:34:09 +03:00
commit 06463ee65c
33 changed files with 1788 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
* text=auto eol=lf
*.patch text eol=lf
*.deb binary
+3
View File
@@ -0,0 +1,3 @@
[submodule "pve-qemu"]
path = pve-qemu
url = https://git.proxmox.com/git/pve-qemu.git
+77
View File
@@ -0,0 +1,77 @@
# qemu-spoof — inject the seed-driven spoof module + anti-detect patches into
# pve-qemu and build the pve-qemu-kvm .deb.
#
# make prepare # init submodules, drop in the module, queue patches, bump changelog
# make deb # build the .deb (runs pve-qemu's own dpkg-buildpackage)
# make clean
#
# Build on Debian trixie (matches pve-qemu 11.0). Never on a production node.
# Override PVE to build against a pve-qemu checkout kept outside this tree:
# make PVE=/path/to/pve-qemu deb
PVE ?= pve-qemu
QSRC := $(PVE)/qemu
PATCHDIR := $(PVE)/debian/patches
SERIES := $(PATCHDIR)/series
# package revision = qemu-spoof commit count: unique + monotonic + orderable per
# content change, so a rebuilt/improved package gets a new version (no 409 on the
# immutable registry) and apt always picks the latest.
SPOOF_REV ?= $(shell git rev-list --count HEAD 2>/dev/null || echo 1)
TAG := qemu-spoof
# Epoch: makes our package version permanently OUTRANK any stock pve-qemu-kvm
# (which has no epoch), so a Proxmox repo update never reverts the spoof. We pull
# upstream fixes deliberately by rebuilding on a newer pve-qemu and re-publishing
# (the epoch carries forward). Set EPOCH= to disable.
EPOCH ?= 1
.PHONY: all prepare deb clean distclean submodule
all: deb
submodule:
@if [ ! -f "$(QSRC)/configure" ]; then \
git submodule update --init --recursive; \
fi
cd $(QSRC) && meson subprojects download
# Idempotent: safe to re-run. pve-qemu copies the qemu/ working tree (cp -a) into
# its build dir, so the module files we drop in here are compiled; the call-site
# wiring is applied on top via debian/patches/series (quilt).
prepare: submodule
@echo ">> install spoof module headers + sources into the qemu tree"
@for h in src/spoof*.h; do install -D -m644 "$$h" "$(QSRC)/include/hw/misc/$$(basename $$h)"; done
@for c in src/spoof*.c; do install -D -m644 "$$c" "$(QSRC)/hw/misc/$$(basename $$c)"; done
@echo ">> register sources in hw/misc/meson.build"
@for c in src/spoof*.c; do b=$$(basename "$$c"); \
grep -q "$$b" $(QSRC)/hw/misc/meson.build || \
echo "system_ss.add(files('$$b'))" >> $(QSRC)/hw/misc/meson.build; \
echo " + $$b"; done
@echo ">> queue anti-detect patches into the series"
@for p in patches/0*.patch; do \
[ -e "$$p" ] || continue; \
b=$$(basename $$p); \
cp -f $$p $(PATCHDIR)/$$b; \
grep -qxF "$$b" $(SERIES) || echo "$$b" >> $(SERIES); \
echo " + $$b"; \
done
@echo ">> bump changelog with epoch $(EPOCH) so it permanently outranks stock pve-qemu-kvm"
@cd $(PVE) && { head -1 debian/changelog | grep -q "$(TAG)" || { \
cur=$$(dpkg-parsechangelog -S Version); \
dch -v "$(EPOCH):$${cur}+$(TAG)$(SPOOF_REV)" "qemu-spoof: seed-driven per-VM hardware identity"; }; }
@cd $(PVE) && head -1 debian/changelog
@echo ">> prepared. run 'make deb'."
deb: prepare
$(MAKE) -C $(PVE) deb
@echo ">> built:"; ls -1 $(PVE)/*.deb 2>/dev/null || ls -1 *.deb 2>/dev/null || true
clean:
-$(MAKE) -C $(PVE) clean 2>/dev/null || true
rm -f *.deb *.buildinfo *.changes
# Drop the injected files + series entries so the submodule is pristine again.
distclean: clean
-cd $(QSRC) && git checkout -- hw/misc/meson.build 2>/dev/null || true
-rm -f $(QSRC)/hw/misc/spoof*.c $(QSRC)/include/hw/misc/spoof*.h
-cd $(PVE) && git checkout -- debian/patches/series debian/changelog 2>/dev/null || true
-rm -f $(PATCHDIR)/0*qemu-spoof* $(PATCHDIR)/9*antidetect*
+98
View File
@@ -0,0 +1,98 @@
# qemu-spoof
Seed-driven, **per-VM** hardware-identity anti-detection for QEMU / Proxmox VE —
a drop-in `pve-qemu-kvm` build. Each VM derives a *coherent, unique* hardware
persona from a per-VM seed, so a fleet of VMs does not share one fingerprint, and
with **no seed it falls back to stock QEMU** (zero behaviour change).
## Why seed-driven (not a static fork)
A hard-coded anti-detect fork gives every VM the *same* firmware/bus identity
(same ACPI OEM, same fw_cfg signature, same PCI IDs, same monitor) — which is
itself a **fleet fingerprint**: many VMs with identical "hardware" correlate
trivially. qemu-spoof instead derives a **coherent persona from a per-VM seed**,
so each VM looks like a different real machine while staying internally
consistent (CPU vendor ↔ chipset ↔ ACPI OEM ↔ board ↔ socket all cohere).
## How it works
A small in-tree module (`hw/misc/spoof.c`, source kept in `src/`) holds pools of
real identities and a pure derivation `seed -> splitmix64 -> pool pick`. Every
anti-detect call site is patched from a hard-coded literal to a getter:
```c
- memcpy(signature, "KVMKVMKVM\0\0\0", 12);
+ memcpy(signature, spoof_kvm_signature("KVMKVMKVM\0\0\0"), 12);
```
The getter returns the seed-derived value, or the **stock default** when no seed
is set — so the patch is inert until a VM opts in.
### Seed input
```
-machine <type>,...,spoof-seed=<string> # preferred (Proxmox: via args)
QEMU_SPOOF_SEED=<string> # env fallback (testing)
```
Same seed → same persona. Mix in a host secret so personas are not guessable from
the vmid. Proxmox: add `spoof-seed=` through the VM `args:` line.
## Layout
```
pve-qemu/ git submodule -> https://git.proxmox.com/git/pve-qemu.git (pinned)
src/spoof*.{c,h} the seed-driven identity module (decomposed by aspect)
patches/ quilt patches injected into pve-qemu/debian/patches/series:
0002 register the spoof-seed machine property
0010+ wire each anti-detect call site to a getter
Makefile prepare -> build the .deb
```
## Build
```sh
git submodule update --init --recursive # pve-qemu + its qemu submodule
make prepare # inject spoof module + patches + changelog
make deb # dpkg-buildpackage -> pve-qemu/*.deb
```
Builds on Debian trixie (matches pve-qemu 11.0). Use a clean builder / WSL, never
a production node. `make deb` produces `pve-qemu-kvm_*_amd64.deb` (+ `-dbgsym`).
## Packaging & delivery
Install the built `.deb` on a Proxmox VE node in place of the stock package, or
serve it from any apt repository (e.g. a Debian package registry).
### Surviving Proxmox updates (epoch + per-commit revision)
The Makefile stamps an **epoch**`1:<upstream>+qemu-spoofN`, where `N` is the
commit count — so the package permanently outranks stock `pve-qemu-kvm` (no
epoch). An `apt upgrade` from the Proxmox repo therefore never reverts the spoof;
you are the source of truth for this package on your nodes. Pull upstream QEMU
fixes deliberately by rebuilding on a newer `pve-qemu` (the epoch carries
forward). Optional belt-and-suspenders apt pin on each node:
```
Package: pve-qemu-kvm
Pin: origin <your-apt-registry-host>
Pin-Priority: 1001
```
## Companion config (machine options, not code)
A few tells are configuration, not code — set them when provisioning a spoofed VM
(alongside the `spoof-seed`):
- `-machine ...,vmport=off` — disable the VMware backdoor port (0x5658)
- prefer real-id emulated devices over virtio where stealth matters: `e1000e` NIC,
SATA/AHCI disk (their PCI vendor:device are real Intel; virtio's `1af4` is a tell
and its id cannot be spoofed without breaking the guest driver)
- GPU: vfio passthrough with `x-pci-vendor-id/...` overrides
- CPU: `host,hidden=1` (+ hv enlightenments for Windows) to back the `spoof-hv` policy
## License
Patches and module are derivative of QEMU and licensed under **GPL v2** (the QEMU
license). See the COPYING file in the QEMU tree.
@@ -0,0 +1,79 @@
qemu-spoof: register per-VM anti-detect machine properties
Adds string machine properties spoof-seed / spoof-hv / spoof-waet /
spoof-vmgenid / spoof-pvpanic on the x86 machine, backed by X86MachineState
fields. The qemu-spoof module (hw/misc/spoof*) reads them via
object_property_get_str(current_machine, "spoof-*"). Inert unless set.
diff --git a/hw/i386/x86.c b/hw/i386/x86.c
index 01872cb..66400fe 100644
--- a/hw/i386/x86.c
+++ b/hw/i386/x86.c
@@ -372,6 +372,26 @@ static void x86_machine_initfn(Object *obj)
x86ms->above_4g_mem_start = 4 * GiB;
}
+/* qemu-spoof: plain string machine properties, read back by the spoof module via
+ * object_property_get_str(current_machine, "spoof-*"). */
+#define X86_SPOOF_PROP(field) \
+ static char *x86_machine_get_##field(Object *obj, Error **errp) \
+ { \
+ return g_strdup(X86_MACHINE(obj)->field); \
+ } \
+ static void x86_machine_set_##field(Object *obj, const char *value, \
+ Error **errp) \
+ { \
+ X86MachineState *x86ms = X86_MACHINE(obj); \
+ g_free(x86ms->field); \
+ x86ms->field = g_strdup(value); \
+ }
+X86_SPOOF_PROP(spoof_seed)
+X86_SPOOF_PROP(spoof_hv)
+X86_SPOOF_PROP(spoof_waet)
+X86_SPOOF_PROP(spoof_vmgenid)
+X86_SPOOF_PROP(spoof_pvpanic)
+
static void x86_machine_class_init(ObjectClass *oc, const void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
@@ -426,6 +446,27 @@ static void x86_machine_class_init(ObjectClass *oc, const void *data)
"in ACPI table header."
"The string may be up to 8 bytes in size");
+ object_class_property_add_str(oc, "spoof-seed",
+ x86_machine_get_spoof_seed, x86_machine_set_spoof_seed);
+ object_class_property_set_description(oc, "spoof-seed",
+ "qemu-spoof: per-VM persona seed (empty = stock QEMU)");
+ object_class_property_add_str(oc, "spoof-hv",
+ x86_machine_get_spoof_hv, x86_machine_set_spoof_hv);
+ object_class_property_set_description(oc, "spoof-hv",
+ "qemu-spoof: hypervisor mode (off|hyperv|hidden)");
+ object_class_property_add_str(oc, "spoof-waet",
+ x86_machine_get_spoof_waet, x86_machine_set_spoof_waet);
+ object_class_property_set_description(oc, "spoof-waet",
+ "qemu-spoof: drop the WAET ACPI table (on|off)");
+ object_class_property_add_str(oc, "spoof-vmgenid",
+ x86_machine_get_spoof_vmgenid, x86_machine_set_spoof_vmgenid);
+ object_class_property_set_description(oc, "spoof-vmgenid",
+ "qemu-spoof: vmgenid policy (keep|mask|hide)");
+ object_class_property_add_str(oc, "spoof-pvpanic",
+ x86_machine_get_spoof_pvpanic, x86_machine_set_spoof_pvpanic);
+ object_class_property_set_description(oc, "spoof-pvpanic",
+ "qemu-spoof: drop the pvpanic device (on|off)");
+
object_class_property_add(oc, X86_MACHINE_BUS_LOCK_RATELIMIT, "uint64_t",
x86_machine_get_bus_lock_ratelimit,
x86_machine_set_bus_lock_ratelimit, NULL, NULL);
diff --git a/include/hw/i386/x86.h b/include/hw/i386/x86.h
index 71fe6b5..a80700f 100644
--- a/include/hw/i386/x86.h
+++ b/include/hw/i386/x86.h
@@ -79,6 +79,9 @@ struct X86MachineState {
char *oem_id;
char *oem_table_id;
+
+ /* qemu-spoof: per-VM anti-detect config, read by hw/misc/spoof*.c */
+ char *spoof_seed, *spoof_hv, *spoof_waet, *spoof_vmgenid, *spoof_pvpanic;
/*
* Address space used by IOAPIC device. All IOAPIC interrupts
* will be translated to MSI messages in the address space.
+41
View File
@@ -0,0 +1,41 @@
qemu-spoof: route ACPI table header identity through the spoof module
acpi_table_begin(): OEM id / OEM table id / creator id now come from
spoof_acpi_*(); build_fadt(): blank the "QEMU" hypervisor-vendor when spoofing.
All inert (return the stock default) unless a spoof-seed is set.
diff --git a/hw/acpi/aml-build.c b/hw/acpi/aml-build.c
index 4b37405..8bc3f4d 100644
--- a/hw/acpi/aml-build.c
+++ b/hw/acpi/aml-build.c
@@ -20,6 +20,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include <glib/gprintf.h>
#include "hw/acpi/aml-build.h"
#include "hw/acpi/acpi.h"
@@ -1725,11 +1726,11 @@ void acpi_table_begin(AcpiTable *desc, GArray *array)
build_append_int_noprefix(array, 0, 4); /* Length */
build_append_int_noprefix(array, desc->rev, 1); /* Revision */
build_append_int_noprefix(array, 0, 1); /* Checksum */
- build_append_padded_str(array, desc->oem_id, 6, '\0'); /* OEMID */
+ build_append_padded_str(array, spoof_acpi_oem_id(desc->oem_id), 6, '\0'); /* OEMID */
/* OEM Table ID */
- build_append_padded_str(array, desc->oem_table_id, 8, '\0');
+ build_append_padded_str(array, spoof_acpi_oem_table_id(desc->oem_table_id), 8, '\0');
build_append_int_noprefix(array, 1, 4); /* OEM Revision */
- g_array_append_vals(array, ACPI_BUILD_APPNAME8, 4); /* Creator ID */
+ g_array_append_vals(array, spoof_acpi_creator_id(ACPI_BUILD_APPNAME8), 4); /* Creator ID */
build_append_int_noprefix(array, 1, 4); /* Creator Revision */
}
@@ -2370,7 +2371,7 @@ void build_fadt(GArray *tbl, BIOSLinker *linker, const AcpiFadtData *f,
}
/* Hypervisor Vendor Identity */
- build_append_padded_str(tbl, "QEMU", 8, '\0');
+ build_append_padded_str(tbl, spoof_enabled() ? "" : "QEMU", 8, '\0');
/* TODO: extra fields need to be added to support revisions above rev6 */
assert(f->rev == 6);
@@ -0,0 +1,39 @@
qemu-spoof: ACPI policy gates (WAET drop, vmgenid hide)
x86_build_acpi_tables(): skip the WAET (Windows ACPI Emulated Devices) table when
spoof_waet_drop(); skip the vmgenid SSDT when spoof_vmgenid_policy()==HIDE. Both
default to off unless a spoof-seed is set.
diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index f622b91..fece5ce 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -21,6 +21,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qapi/error.h"
#include "qobject/qnum.h"
#include "acpi-build.h"
@@ -2000,7 +2001,7 @@ void acpi_build(AcpiBuildTables *tables, MachineState *machine)
#endif
vmgenid_dev = find_vmgenid_dev();
- if (vmgenid_dev) {
+ if (vmgenid_dev && spoof_vmgenid_policy() != SPOOF_VGID_HIDE) {
acpi_add_table(table_offsets, tables_blob);
vmgenid_build_acpi(VMGENID(vmgenid_dev), tables_blob,
tables->vmgenid, tables->linker, x86ms->oem_id);
@@ -2075,8 +2076,10 @@ void acpi_build(AcpiBuildTables *tables, MachineState *machine)
x86ms->oem_id, x86ms->oem_table_id, &pcms->cxl_devices_state);
}
- acpi_add_table(table_offsets, tables_blob);
- build_waet(tables_blob, tables->linker, x86ms->oem_id, x86ms->oem_table_id);
+ if (!spoof_waet_drop()) { /* qemu-spoof: WAET = emulated-devices tell */
+ acpi_add_table(table_offsets, tables_blob);
+ build_waet(tables_blob, tables->linker, x86ms->oem_id, x86ms->oem_table_id);
+ }
/* Add tables supplied by user (if any) */
for (u = acpi_table_first(); u; u = acpi_table_next(u)) {
+70
View File
@@ -0,0 +1,70 @@
qemu-spoof: scrub device ACPI _HIDs (vmgenid / pvpanic / fw_cfg)
vmgenid: _HID QEMUVGID -> MSFT0002 when vmgenid policy = mask. pvpanic: omit the
QEMU0001 _HID node when spoof_pvpanic_hide(). fw_cfg: _HID QEMU0002 ->
spoof_fwcfg_acpi_devid(). Inert unless a spoof-seed is set.
diff --git a/hw/acpi/vmgenid.c b/hw/acpi/vmgenid.c
index 70ad029..7f95d79 100644
--- a/hw/acpi/vmgenid.c
+++ b/hw/acpi/vmgenid.c
@@ -11,6 +11,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "hw/acpi/acpi.h"
@@ -53,7 +54,8 @@ void vmgenid_build_acpi(VmGenIdState *vms, GArray *table_data, GArray *guid,
build_append_named_dword(ssdt->buf, "VGIA");
scope = aml_scope("\\_SB");
dev = aml_device("VGEN");
- aml_append(dev, aml_name_decl("_HID", aml_string("QEMUVGID")));
+ aml_append(dev, aml_name_decl("_HID", aml_string("%s",
+ spoof_vmgenid_policy() == SPOOF_VGID_MASK ? "MSFT0002" : "QEMUVGID")));
aml_append(dev, aml_name_decl("_CID", aml_string("VM_Gen_Counter")));
aml_append(dev, aml_name_decl("_DDN", aml_string("VM_Gen_Counter")));
diff --git a/hw/misc/pvpanic-isa.c b/hw/misc/pvpanic-isa.c
index 85fb7da..4b6854e 100644
--- a/hw/misc/pvpanic-isa.c
+++ b/hw/misc/pvpanic-isa.c
@@ -13,6 +13,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qemu/module.h"
#include "system/runstate.h"
@@ -69,6 +70,10 @@ static void build_pvpanic_isa_aml(AcpiDevAmlIf *adev, Aml *scope)
PVPanicISAState *s = PVPANIC_ISA_DEVICE(adev);
Aml *dev = aml_device("PEVT");
+ if (spoof_pvpanic_hide()) { /* qemu-spoof: omit the QEMU0001 _HID node */
+ return;
+ }
+
aml_append(dev, aml_name_decl("_HID", aml_string("QEMU0001")));
crs = aml_resource_template();
diff --git a/hw/nvram/fw_cfg-acpi.c b/hw/nvram/fw_cfg-acpi.c
index 2e6ef89..8bb7be7 100644
--- a/hw/nvram/fw_cfg-acpi.c
+++ b/hw/nvram/fw_cfg-acpi.c
@@ -5,13 +5,14 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "hw/nvram/fw_cfg_acpi.h"
#include "hw/acpi/aml-build.h"
void fw_cfg_acpi_dsdt_add(Aml *scope, const MemMapEntry *fw_cfg_memmap)
{
Aml *dev = aml_device("FWCF");
- aml_append(dev, aml_name_decl("_HID", aml_string("QEMU0002")));
+ aml_append(dev, aml_name_decl("_HID", aml_string("%s", spoof_fwcfg_acpi_devid("QEMU0002"))));
/* device present, functioning, decoding, not shown in UI */
aml_append(dev, aml_name_decl("_STA", aml_int(0xB)));
aml_append(dev, aml_name_decl("_CCA", aml_int(1)));
+61
View File
@@ -0,0 +1,61 @@
qemu-spoof: CPUID — KVM signature + leaf 0x16 frequency
CPUID 0x40000000 KVM signature "KVMKVMKVM" -> spoof_kvm_signature() (vendor-
anchored GenuineIntel/AuthenticAMD). CPUID leaf 0x16 (Processor Frequency Info),
which stock QEMU returns as zeros, is filled from spoof_cpu_{base,max,bus}_mhz().
Inert unless a spoof-seed is set. (hv-mode kvm=off / hypervisor-bit handling is
done via the Proxmox cpu flags, see README.)
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index c6fd1dc..1093326 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -18,6 +18,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qemu/units.h"
#include "qemu/cutils.h"
#include "qemu/qemu-print.h"
@@ -9166,6 +9167,20 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count,
*ecx = 0;
*edx = 0;
break;
+ case 0x16:
+ /* qemu-spoof: Processor Frequency Information (stock QEMU returns 0). */
+ if (spoof_enabled()) {
+ *eax = spoof_cpu_base_mhz(0) & 0xffff;
+ *ebx = spoof_cpu_max_mhz(0) & 0xffff;
+ *ecx = spoof_cpu_bus_mhz(0) & 0xffff;
+ *edx = 0;
+ } else {
+ *eax = 0;
+ *ebx = 0;
+ *ecx = 0;
+ *edx = 0;
+ }
+ break;
default:
/* reserved values: zero */
*eax = 0;
diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
index 9e35288..5b112b5 100644
--- a/target/i386/kvm/kvm.c
+++ b/target/i386/kvm/kvm.c
@@ -13,6 +13,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qapi/qapi-events-run-state.h"
#include "qapi/error.h"
#include "qapi/visitor.h"
@@ -2380,7 +2381,7 @@ int kvm_arch_init_vcpu(CPUState *cs)
abort();
#endif
} else if (cpu->expose_kvm) {
- memcpy(signature, "KVMKVMKVM\0\0\0", 12);
+ memcpy(signature, spoof_kvm_signature("KVMKVMKVM\0\0\0"), 12);
c = &cpuid_data.entries[cpuid_i++];
c->function = KVM_CPUID_SIGNATURE | kvm_base;
c->eax = KVM_CPUID_FEATURES | kvm_base;
+34
View File
@@ -0,0 +1,34 @@
qemu-spoof: fw_cfg selector + DMA signatures
FW_CFG_SIGNATURE "QEMU" -> spoof_fwcfg_sig(); the DMA signature ("QEMU CFG") ->
spoof_fwcfg_dma_sig(). Inert unless a spoof-seed is set.
diff --git a/hw/nvram/fw_cfg.c b/hw/nvram/fw_cfg.c
index 1d7d835..3b29e96 100644
--- a/hw/nvram/fw_cfg.c
+++ b/hw/nvram/fw_cfg.c
@@ -23,6 +23,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qemu/datadir.h"
#include "system/system.h"
#include "system/dma.h"
@@ -442,7 +443,7 @@ static uint64_t fw_cfg_dma_mem_read(void *opaque, hwaddr addr,
unsigned size)
{
/* Return a signature value (and handle various read sizes) */
- return extract64(FW_CFG_DMA_SIGNATURE, (8 - addr - size) * 8, size * 8);
+ return extract64(spoof_fwcfg_dma_sig(FW_CFG_DMA_SIGNATURE), (8 - addr - size) * 8, size * 8);
}
static void fw_cfg_dma_mem_write(void *opaque, hwaddr addr,
@@ -1002,7 +1003,7 @@ static void fw_cfg_common_realize(DeviceState *dev, Error **errp)
return;
}
- fw_cfg_add_bytes(s, FW_CFG_SIGNATURE, (char *)"QEMU", 4);
+ fw_cfg_add_bytes(s, FW_CFG_SIGNATURE, (char *)spoof_fwcfg_sig("QEMU"), 4);
fw_cfg_add_bytes(s, FW_CFG_UUID, &qemu_uuid, 16);
fw_cfg_add_i16(s, FW_CFG_NOGRAPHIC, (uint16_t)!machine->enable_graphics);
fw_cfg_add_i16(s, FW_CFG_BOOT_MENU, (uint16_t)(machine->boot_config.has_menu && machine->boot_config.menu));
+27
View File
@@ -0,0 +1,27 @@
qemu-spoof: clear the SMBIOS type0 "VM" characteristic bit
type0 bios-characteristics-extension byte sets bit 0x10 ("VM") for QEMU; suppress
it when spoof_smbios_hide_vm(). (type1/2/3 manufacturer/product/serial are the
config layer; type4 socket / type11 OEM-strings / type17 memory wiring is a
follow-up.) Inert unless a spoof-seed is set.
diff --git a/hw/smbios/smbios.c b/hw/smbios/smbios.c
index 7d71418..4c39b5a 100644
--- a/hw/smbios/smbios.c
+++ b/hw/smbios/smbios.c
@@ -16,6 +16,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qemu/units.h"
#include "qemu/bswap.h"
#include "qapi/error.h"
@@ -583,7 +584,7 @@ static void smbios_build_type_0_table(void)
if (smbios_type0.uefi) {
t->bios_characteristics_extension_bytes[1] |= 0x08; /* |= UEFI */
}
- if (smbios_type0.vm) {
+ if (smbios_type0.vm && !spoof_smbios_hide_vm()) {
t->bios_characteristics_extension_bytes[1] |= 0x10; /* |= VM */
}
+65
View File
@@ -0,0 +1,65 @@
qemu-spoof: EDID monitor identity (with weak fallbacks for the qemu-edid tool)
vendor/name/model/serial/manufacture-date now come from spoof_edid_*(). Since
edid-generate.c is also compiled into the standalone qemu-edid tool (which does
not link the spoof module), weak default definitions of spoof_edid_* are provided
here so the tool links; the strong spoof-display.c definitions override them in
the system emulator. Inert unless a spoof-seed is set.
diff --git a/hw/display/edid-generate.c b/hw/display/edid-generate.c
index 2cb8196..170f2b8 100644
--- a/hw/display/edid-generate.c
+++ b/hw/display/edid-generate.c
@@ -7,6 +7,20 @@
#include "qemu/osdep.h"
#include "qemu/bswap.h"
#include "hw/display/edid.h"
+#include "hw/misc/spoof.h"
+
+/*
+ * Weak fallbacks: the standalone qemu-edid tool compiles this file but does NOT
+ * link the spoof module, so it would have undefined spoof_edid_* references.
+ * These defaults let it link; in the system emulator the strong spoof-display.c
+ * definitions override them.
+ */
+__attribute__((weak)) const char *spoof_edid_vendor(const char *def) { return def; }
+__attribute__((weak)) const char *spoof_edid_name(const char *def) { return def; }
+__attribute__((weak)) uint16_t spoof_edid_model(uint16_t def) { return def; }
+__attribute__((weak)) uint32_t spoof_edid_serial(uint32_t def) { return def; }
+__attribute__((weak)) int spoof_edid_week(int def) { return def; }
+__attribute__((weak)) int spoof_edid_year(int def) { return def; }
static const struct edid_mode {
uint32_t xres;
@@ -394,10 +408,10 @@ void qemu_edid_generate(uint8_t *edid, size_t size,
/* =============== set defaults =============== */
if (!info->vendor || strlen(info->vendor) != 3) {
- info->vendor = "RHT";
+ info->vendor = spoof_edid_vendor("RHT");
}
if (!info->name) {
- info->name = "QEMU Monitor";
+ info->name = spoof_edid_name("QEMU Monitor");
}
if (!info->prefx) {
info->prefx = 1280;
@@ -449,15 +463,15 @@ void qemu_edid_generate(uint8_t *edid, size_t size,
uint16_t vendor_id = ((((info->vendor[0] - '@') & 0x1f) << 10) |
(((info->vendor[1] - '@') & 0x1f) << 5) |
(((info->vendor[2] - '@') & 0x1f) << 0));
- uint16_t model_nr = 0x1234;
- uint32_t serial_nr = info->serial ? atoi(info->serial) : 0;
+ uint16_t model_nr = spoof_edid_model(0x1234);
+ uint32_t serial_nr = spoof_edid_serial(info->serial ? atoi(info->serial) : 0);
stw_be_p(edid + 8, vendor_id);
stw_le_p(edid + 10, model_nr);
stl_le_p(edid + 12, serial_nr);
/* manufacture week and year */
- edid[16] = 42;
- edid[17] = 2014 - 1990;
+ edid[16] = spoof_edid_week(42);
+ edid[17] = spoof_edid_year(2014) - 1990;
/* edid version */
edid[18] = 1;
+78
View File
@@ -0,0 +1,78 @@
qemu-spoof: storage identity (IDE + NVMe)
IDE: model "QEMU HARDDISK"/"QEMU DVD-ROM", firmware rev, and the auto serial now
route through spoof_disk_*/spoof_cdrom_model. NVMe: model "QEMU NVMe Ctrl",
serial, and the firmware revision (was QEMU_VERSION -- an information leak) now
route through spoof_nvme_*. Inert unless a spoof-seed is set.
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 7a15d6c..ad5251f 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -24,6 +24,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "hw/core/irq.h"
#include "hw/isa/isa.h"
#include "migration/vmstate.h"
@@ -2641,19 +2642,23 @@ int ide_init_drive(IDEState *s, IDEDevice *dev, IDEDriveKind kind, Error **errp)
} else {
snprintf(s->drive_serial_str, sizeof(s->drive_serial_str),
"QM%05d", s->drive_serial);
+ if (spoof_enabled()) { /* qemu-spoof: brand-correct serial */
+ pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str),
+ spoof_disk_serial(s->drive_serial_str));
+ }
}
if (dev->model) {
pstrcpy(s->drive_model_str, sizeof(s->drive_model_str), dev->model);
} else {
switch (kind) {
case IDE_CD:
- strcpy(s->drive_model_str, "QEMU DVD-ROM");
+ strcpy(s->drive_model_str, spoof_cdrom_model("QEMU DVD-ROM"));
break;
case IDE_CFATA:
strcpy(s->drive_model_str, "QEMU MICRODRIVE");
break;
default:
- strcpy(s->drive_model_str, "QEMU HARDDISK");
+ strcpy(s->drive_model_str, spoof_disk_model("QEMU HARDDISK"));
break;
}
}
@@ -2661,7 +2666,7 @@ int ide_init_drive(IDEState *s, IDEDevice *dev, IDEDriveKind kind, Error **errp)
if (dev->version) {
pstrcpy(s->version, sizeof(s->version), dev->version);
} else {
- pstrcpy(s->version, sizeof(s->version), QEMU_HW_VERSION);
+ pstrcpy(s->version, sizeof(s->version), spoof_disk_fw(QEMU_HW_VERSION));
}
ide_reset(s);
diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c
index be6c702..575085a 100644
--- a/hw/nvme/ctrl.c
+++ b/hw/nvme/ctrl.c
@@ -194,6 +194,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qemu/cutils.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
@@ -9096,9 +9097,9 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev)
id->vid = cpu_to_le16(pci_get_word(pci_conf + PCI_VENDOR_ID));
id->ssvid = cpu_to_le16(pci_get_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID));
- strpadcpy((char *)id->mn, sizeof(id->mn), "QEMU NVMe Ctrl", ' ');
- strpadcpy((char *)id->fr, sizeof(id->fr), QEMU_VERSION, ' ');
- strpadcpy((char *)id->sn, sizeof(id->sn), n->params.serial, ' ');
+ strpadcpy((char *)id->mn, sizeof(id->mn), spoof_nvme_model("QEMU NVMe Ctrl"), ' ');
+ strpadcpy((char *)id->fr, sizeof(id->fr), spoof_nvme_fw(QEMU_VERSION), ' ');
+ strpadcpy((char *)id->sn, sizeof(id->sn), spoof_nvme_serial(n->params.serial), ' ');
id->cntlid = cpu_to_le16(n->cntlid);
+52
View File
@@ -0,0 +1,52 @@
qemu-spoof: OEM-brand emulated PCI subsystem ids (realize-time)
QEMU stamps a constant subsystem id (default 1b36:1100) on every emulated PCI
device -- a fleet fingerprint. The subsystem id is not used for guest driver
binding, so override it at realize time with an OEM-branded SVID/SSID (chipset
vendor + per-role device id) for non-bridge devices. Red Hat/virtio (0x1af4) is
skipped: legacy-virtio encodes the device type in the subsystem id, so changing
it would break the guest driver (virtio is de-fingerprinted by device choice
instead). vfio passthrough overwrites this with the real hardware SSID afterwards.
This is the realize-time override path the deferred 0018 note called for; it does
NOT touch vendor/device ids (those ARE driver-binding and would break drivers).
Inert unless a spoof-seed is set.
diff --git a/hw/pci/pci.c b/hw/pci/pci.c
index 2c3657d..e3da2ab 100644
--- a/hw/pci/pci.c
+++ b/hw/pci/pci.c
@@ -23,6 +23,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qemu/datadir.h"
#include "qemu/units.h"
#include "hw/core/irq.h"
@@ -1417,6 +1418,27 @@ static PCIDevice *do_pci_register_device(PCIDevice *pci_dev,
assert(!pc->subsystem_vendor_id);
assert(!pc->subsystem_id);
}
+
+ /*
+ * qemu-spoof: OEM-brand the subsystem id of emulated devices. Real boards
+ * carry the vendor's SSID; QEMU stamps a constant 1b36:1100 default on every
+ * device, which is itself a fleet fingerprint. The subsystem id does not take
+ * part in driver binding, so this is safe -- EXCEPT for Red Hat/virtio
+ * (0x1af4), where legacy-virtio encodes the device type in the subsystem id;
+ * skip those (virtio is de-fingerprinted by device choice, not id spoofing).
+ * vfio passthrough overwrites this later with the real hardware SSID. Inert
+ * unless a spoof-seed is set.
+ */
+ if (spoof_enabled() && !is_bridge &&
+ pci_get_word(pci_dev->config + PCI_VENDOR_ID) != 0x1af4) {
+ const char *role = object_get_typename(OBJECT(pci_dev));
+ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_VENDOR_ID,
+ spoof_pci_subvendor(
+ pci_get_word(pci_dev->config + PCI_SUBSYSTEM_VENDOR_ID)));
+ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_ID,
+ spoof_pci_subdevice(role,
+ pci_get_word(pci_dev->config + PCI_SUBSYSTEM_ID)));
+ }
pci_init_cmask(pci_dev);
pci_init_wmask(pci_dev);
pci_init_w1cmask(pci_dev);
+46
View File
@@ -0,0 +1,46 @@
qemu-spoof: machine description string
m->desc "Standard PC (Q35...)" / "(i440FX...)" -> spoof_machine_desc() (the
platform-anchored board model). Inert unless a spoof-seed is set.
diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c
index 4d71e0d..d5561dd 100644
--- a/hw/i386/pc_piix.c
+++ b/hw/i386/pc_piix.c
@@ -23,6 +23,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include CONFIG_DEVICES
#include "qemu/units.h"
@@ -408,7 +409,7 @@ static void pc_i440fx_machine_options(MachineClass *m)
pcmc->default_cpu_version = 1;
m->family = "pc_piix";
- m->desc = "Standard PC (i440FX + PIIX, 1996)";
+ m->desc = spoof_machine_desc("Standard PC (i440FX + PIIX, 1996)");
m->default_machine_opts = "firmware=bios-256k.bin";
m->default_display = "std";
m->default_nic = "e1000";
diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c
index cb23322..65864b0 100644
--- a/hw/i386/pc_q35.c
+++ b/hw/i386/pc_q35.c
@@ -29,6 +29,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qemu/units.h"
#include "hw/acpi/acpi.h"
#include "hw/char/parallel-isa.h"
@@ -346,7 +347,7 @@ static void pc_q35_machine_options(MachineClass *m)
pcmc->default_cpu_version = 1;
m->family = "pc_q35";
- m->desc = "Standard PC (Q35 + ICH9, 2009)";
+ m->desc = spoof_machine_desc("Standard PC (Q35 + ICH9, 2009)");
m->units_per_default_bus = 1;
m->default_machine_opts = "firmware=bios-256k.bin";
m->default_display = "std";
+81
View File
@@ -0,0 +1,81 @@
qemu-spoof: SMBIOS type3/4/11/17 identity
Wire the remaining SMBIOS getters (module already derives them):
- type3 chassis type (Desktop/Notebook, from the persona machine class)
- type4 processor socket designation + processor manufacturer (CPU-vendor anchored)
- type11 OEM strings: QEMU emits no type 11 at all (a tell); emit the spoofed
OEM string when config provided none
- type17 memory manufacturer / serial / part (brand-correct, per-unit)
All inert unless a spoof-seed is set (getters return the stock value when off).
The type0 "VM" bit is handled by 0015; the spoof.h include is added there.
diff --git a/hw/smbios/smbios.c b/hw/smbios/smbios.c
index 7d71418..0d8e569 100644
--- a/hw/smbios/smbios.c
+++ b/hw/smbios/smbios.c
@@ -656,7 +656,7 @@ static void smbios_build_type_3_table(void)
SMBIOS_BUILD_TABLE_PRE(3, T3_BASE, true); /* required */
SMBIOS_TABLE_SET_STR(3, manufacturer_str, type3.manufacturer);
- t->type = 0x01; /* Other */
+ t->type = spoof_smbios_chassis_type(0x01); /* Other (3=Desktop, 10=Notebook) */
SMBIOS_TABLE_SET_STR(3, version_str, type3.version);
SMBIOS_TABLE_SET_STR(3, serial_number_str, type3.serial);
SMBIOS_TABLE_SET_STR(3, asset_tag_number_str, type3.asset);
@@ -690,11 +690,13 @@ static void smbios_build_type_4_table(MachineState *ms, unsigned instance,
SMBIOS_BUILD_TABLE_PRE_SIZE(4, T4_BASE + instance,
true, tbl_len); /* required */
- snprintf(sock_str, sizeof(sock_str), "%s%2x", type4.sock_pfx, instance);
+ snprintf(sock_str, sizeof(sock_str), "%s%2x",
+ spoof_smbios_cpu_socket(type4.sock_pfx), instance);
SMBIOS_TABLE_SET_STR(4, socket_designation_str, sock_str);
t->processor_type = 0x03; /* CPU */
t->processor_family = 0xfe; /* use Processor Family 2 field */
- SMBIOS_TABLE_SET_STR(4, processor_manufacturer_str, type4.manufacturer);
+ SMBIOS_TABLE_SET_STR(4, processor_manufacturer_str,
+ spoof_smbios_cpu_manufacturer(type4.manufacturer));
if (type4.processor_id == 0) {
t->processor_id[0] = cpu_to_le32(smbios_cpuid_version);
t->processor_id[1] = cpu_to_le32(smbios_cpuid_features);
@@ -825,13 +827,23 @@ static void smbios_build_type_11_table(void)
{
char count_str[128];
size_t i;
+ /* qemu-spoof: real machines expose OEM strings here; QEMU emits no type 11 at
+ * all, which is itself a tell. If config gave none, emit the spoofed one. */
+ const char *spoof_oem = spoof_smbios_oem_string(NULL);
- if (type11.nvalues == 0) {
+ if (type11.nvalues == 0 && !spoof_oem) {
return;
}
SMBIOS_BUILD_TABLE_PRE(11, T11_BASE, true); /* required */
+ if (type11.nvalues == 0) {
+ t->count = 1;
+ SMBIOS_TABLE_SET_STR_LIST(11, spoof_oem);
+ SMBIOS_BUILD_TABLE_POST;
+ return;
+ }
+
snprintf(count_str, sizeof(count_str), "%zu", type11.nvalues);
t->count = type11.nvalues;
@@ -900,10 +912,13 @@ static void smbios_build_type_17_table(unsigned instance, uint64_t size)
t->memory_type = 0x07; /* RAM */
t->type_detail = cpu_to_le16(0x02); /* Other */
t->speed = cpu_to_le16(type17.speed);
- SMBIOS_TABLE_SET_STR(17, manufacturer_str, type17.manufacturer);
- SMBIOS_TABLE_SET_STR(17, serial_number_str, type17.serial);
+ SMBIOS_TABLE_SET_STR(17, manufacturer_str,
+ spoof_smbios_mem_manufacturer(type17.manufacturer));
+ SMBIOS_TABLE_SET_STR(17, serial_number_str,
+ spoof_smbios_mem_serial(type17.serial));
SMBIOS_TABLE_SET_STR(17, asset_tag_number_str, type17.asset);
- SMBIOS_TABLE_SET_STR(17, part_number_str, type17.part);
+ SMBIOS_TABLE_SET_STR(17, part_number_str,
+ spoof_smbios_mem_part(type17.part));
t->attributes = 0; /* Unknown */
t->configured_clock_speed = t->speed; /* reuse value for max speed */
t->minimum_voltage = cpu_to_le16(0); /* Unknown */
+62
View File
@@ -0,0 +1,62 @@
qemu-spoof: storage extra identity (IDE WWN/rotation + NVMe EUI64/NGUID)
Complements 0017 (model/serial/fw). When a spoof-seed is set and these are unset:
- IDE: a NAA-5 World Wide Name (brand OUI) and the ATA nominal rotation rate
(1 = SSD, else RPM matching the spoofed model) -- QEMU leaves both at 0/absent.
- NVMe: a real-OUI EUI-64 and a 16-byte NGUID -- QEMU advertises none unless asked,
itself a tell. Inert unless a spoof-seed is set. (ide/core.c spoof.h include is
added by 0017; ns.c adds it here.)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 7a15d6c..6dcd4f9 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -2617,6 +2617,14 @@ int ide_init_drive(IDEState *s, IDEDevice *dev, IDEDriveKind kind, Error **errp)
s->chs_trans = dev->chs_trans;
s->nb_sectors = nb_sectors;
s->wwn = dev->wwn;
+ if (spoof_enabled()) { /* qemu-spoof: NAA WWN + rotation rate */
+ if (!s->wwn) {
+ s->wwn = spoof_disk_wwn(0);
+ }
+ if (kind != IDE_CD && dev->rotation_rate == 0) {
+ dev->rotation_rate = spoof_disk_rotation(0);
+ }
+ }
/* The SMART values should be preserved across power cycles
but they aren't. */
s->smart_enabled = 1;
diff --git a/hw/nvme/ns.c b/hw/nvme/ns.c
index b0106ea..723216b 100644
--- a/hw/nvme/ns.c
+++ b/hw/nvme/ns.c
@@ -13,6 +13,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/misc/spoof.h"
#include "qemu/units.h"
#include "qemu/cutils.h"
#include "qemu/error-report.h"
@@ -91,6 +92,22 @@ static int nvme_ns_init(NvmeNamespace *ns, Error **errp)
if (!ns->params.eui64 && ns->params.eui64_default) {
ns->params.eui64 = ns_count + NVME_EUI64_DEFAULT;
}
+ if (spoof_enabled()) { /* qemu-spoof: real OUI EUI-64 + NGUID identifiers */
+ int zi;
+ bool nguid_zero = true;
+ if (!ns->params.eui64) {
+ ns->params.eui64 = spoof_nvme_eui64(0);
+ }
+ for (zi = 0; zi < 16; zi++) {
+ if (ns->params.nguid.data[zi]) {
+ nguid_zero = false;
+ break;
+ }
+ }
+ if (nguid_zero) {
+ spoof_nvme_nguid(ns->params.nguid.data);
+ }
+ }
/* simple copy */
id_ns->mssrl = cpu_to_le16(ns->params.mssrl);
+19
View File
@@ -0,0 +1,19 @@
qemu-spoof: CPU microcode revision (IA32_UCODE_REV)
QEMU/KVM default ucode_rev (Intel 0x1_00000000, AMD 0x01000065) is constant and
recognisable. Route it through spoof_cpu_microcode, which returns a plausible
vendor-positioned revision (Intel in MSR bits 63:32, AMD in 31:0) anchored to the
persona CPU vendor. Inert unless a spoof-seed is set. (target/i386 spoof.h include
is added by 0013.)
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index c6fd1dc..96cbedc 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -10026,6 +10026,7 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp)
} else {
cpu->ucode_rev = 0x100000000ULL;
}
+ cpu->ucode_rev = spoof_cpu_microcode(cpu->ucode_rev); /* qemu-spoof */
}
/*
+53
View File
@@ -0,0 +1,53 @@
# Anti-detect patch series
Quilt patches injected (by `make prepare`) into `pve-qemu/debian/patches/series`,
applied in order during the `.deb` build. Each wires a stock QEMU literal to a
`spoof_*()` getter from the in-tree module (`hw/misc/spoof.{c,h}`, dropped in by
the Makefile). The getter returns the seed-derived value, or the **stock default**
when no `spoof-seed` is set — so the whole series is inert until a VM opts in.
Authored against the pinned `pve-qemu/qemu` tree (QEMU 11.0) so the context lines
match exactly. Naming: `0002` = infra, `0010+` = one aspect each.
| patch | covers |
|---|---|
| 0002-x86-machine-spoof-properties | register `spoof-seed` / `spoof-hv` / `spoof-waet` / `spoof-vmgenid` / `spoof-pvpanic` machine props |
| 0010-acpi-table-header | ACPI OEM id / table id / creator + FADT hypervisor-vendor |
| 0011-acpi-policy-waet-vmgenid | drop the WAET table; skip the vmgenid SSDT when policy = hide |
| 0012-acpi-device-hids | vmgenid `_HID` (mask), pvpanic `_HID` (hide), fw_cfg `_HID` |
| 0013-cpuid-kvm-sig-freq | CPUID KVM signature + leaf 0x16 frequency |
| 0014-fwcfg-signatures | fw_cfg selector + DMA signatures |
| 0015-smbios-vm-bit | clear the SMBIOS type0 "VM" characteristic bit |
| 0016-edid-monitor | EDID vendor / name / model / serial / manufacture-date |
| 0017-storage-identity | IDE model / serial / fw / cdrom + NVMe model / serial / fw |
| 0018-pci-subsystem-id | realize-time OEM-brand of emulated PCI subsystem ids (SVID/SSID) |
| 0019-machine-desc | machine `desc` string → platform board model |
| 0021-smbios-identity | SMBIOS type3 chassis / type4 socket + cpu-mfr / type11 OEM / type17 memory |
| 0022-storage-extra | IDE WWN + rotation rate; NVMe EUI-64 + NGUID |
| 0023-cpu-microcode | CPU microcode revision (IA32_UCODE_REV), vendor-positioned |
## Why 0018 is subsystem-only (the PCI-id problem)
The PCI **vendor:device** id IS the guest driver-binding contract: NetKVM binds
`1af4:1000`, the ivshmem driver binds `1af4:1110`, the GPU driver binds its id.
Spoofing those breaks the device (no driver matches). So `0018` only rewrites the
**subsystem** id (not used for binding) — and even that skips `0x1af4`, because
*legacy*-virtio encodes the device type in the subsystem id. Class-bound devices
(NVMe / xHCI / AHCI bind by class code, not id) *could* take a real vendor:device,
but that needs a per-role allowlist. virtio / ivshmem / GPU ids are instead
de-fingerprinted by **device choice** (`e1000e` / SATA) and vfio `x-pci-*`, not by
id spoofing.
## Out of scope (handled by config or upstream, not these patches)
- **PCI vendor:device id** for class-bound devices (qemu-nvme `1b36:0010` → a real
NVMe vendor; `nec-usb-xhci`): safe in principle, would need a per-role allowlist in
the `0018` realize hook.
- **HDA codec id** (`QEMU_HDA_ID_VENDOR 0x1af4`): a compile-time constant baked into a
`static const` codec descriptor; spoofing needs a runtime override of the
GET_PARAMETER verb response.
- **vmport/vmmouse** (VMware backdoor port 0x5658): disable via `-machine vmport=off`
(a machine option).
- **hv-mode / hypervisor bit**: via the CPU model flags (`cpu: host,hidden=1` + hv
enlightenments). The module's `spoof_hv_mode` policy is the intent; enforcement is
configuration.
Submodule
+1
Submodule pve-qemu added at 817dab1cda
+22
View File
@@ -0,0 +1,22 @@
/* spoof-acpi.c — ACPI table header identity. OEM id / table id come from the
* PLATFORM anchor (spoof-platform.c) so they cohere with the machine / BIOS /
* baseboard. Only the creator id (the ASL compiler) is its own pool. */
#include "qemu/osdep.h"
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
/* 4-byte creator id: real machines are overwhelmingly the Intel ASL compiler. */
static const char *const CREATOR[] = { "INTL", "INTL", "INTL", "MSFT", "AMI ", "ACPI", "PTL " };
const char *spoof_acpi_oem_id(const char *def)
{
return spoof_on() ? spoof_plat_acpi_oem() : def;
}
const char *spoof_acpi_oem_table_id(const char *def)
{
return spoof_on() ? spoof_plat_acpi_table() : def;
}
const char *spoof_acpi_creator_id(const char *def)
{
return spoof_on() ? SPOOF_PICK("acpi.creator", CREATOR) : def;
}
+22
View File
@@ -0,0 +1,22 @@
/* spoof-audio.c — HDA codec identity (real machines = Realtek ALC, not a QEMU id). */
#include "qemu/osdep.h"
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
/* 32-bit HDA codec vendor/device id (Realtek vendor 0x10EC). */
static const uint32_t HDA_ID[] = {
0x10EC0892, 0x10EC1220, 0x10EC0887, 0x10EC0255, 0x10EC0256, 0x10EC0897, 0x10EC0233,
};
static const char *const HDA_NAME[] = {
"Realtek ALC892", "Realtek ALC1220", "Realtek ALC887", "Realtek ALC255",
"Realtek ALC256", "Realtek ALC897", "Realtek ALC233",
};
uint32_t spoof_hda_vendor_id(uint32_t def)
{
return spoof_on() ? HDA_ID[spoof_field("hda.id") % ARRAY_SIZE(HDA_ID)] : def;
}
const char *spoof_hda_name(const char *def)
{
return spoof_on() ? SPOOF_PICK("hda.name", HDA_NAME) : def;
}
+128
View File
@@ -0,0 +1,128 @@
/*
* spoof-core.c — seed/config acquisition + the deterministic derivation engine
* + the policy knobs (hv mode / WAET / vmgenid). See spoof-core.h, spoof.h.
*/
#include "qemu/osdep.h"
#include "qemu/cutils.h" /* pstrcpy */
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
#include "hw/core/boards.h" /* current_machine */
#include "qom/object.h"
const char SPOOF_A36[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char SPOOF_LET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char SPOOF_DIG[] = "0123456789";
/* ---- config: machine property first, env var as a test fallback ----------- */
static char g_seed[256];
static bool g_ready, g_enabled;
static char g_hv[16], g_waet[16], g_vgid[16], g_pvp[16];
static void read_prop(const char *prop, const char *env, char *out, size_t n)
{
out[0] = '\0';
if (current_machine && object_property_find(OBJECT(current_machine), prop)) {
char *v = object_property_get_str(OBJECT(current_machine), prop, NULL);
if (v) { pstrcpy(out, n, v); g_free(v); }
}
if (!out[0]) {
const char *e = getenv(env);
if (e) { pstrcpy(out, n, e); }
}
}
static void config_load(void)
{
if (g_ready) {
return;
}
read_prop("spoof-seed", "QEMU_SPOOF_SEED", g_seed, sizeof(g_seed));
read_prop("spoof-hv", "QEMU_SPOOF_HV", g_hv, sizeof(g_hv));
read_prop("spoof-waet", "QEMU_SPOOF_WAET", g_waet, sizeof(g_waet));
read_prop("spoof-vmgenid", "QEMU_SPOOF_VMGENID", g_vgid, sizeof(g_vgid));
read_prop("spoof-pvpanic", "QEMU_SPOOF_PVPANIC", g_pvp, sizeof(g_pvp));
g_enabled = g_seed[0] != '\0';
g_ready = true;
}
bool spoof_on(void) { config_load(); return g_enabled; }
bool spoof_enabled(void) { return spoof_on(); }
/* ---- deterministic derivation: fnv1a(seed|key) -> splitmix64 -------------- */
static uint64_t fnv1a(const char *s)
{
uint64_t h = 1469598103934665603ULL;
for (; *s; s++) { h ^= (uint8_t)*s; h *= 1099511628211ULL; }
return h;
}
static uint64_t mix(uint64_t x)
{
x += 0x9E3779B97F4A7C15ULL;
x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ULL;
x = (x ^ (x >> 27)) * 0x94D049BB133111EBULL;
return x ^ (x >> 31);
}
uint64_t spoof_field(const char *key)
{
config_load();
return mix(fnv1a(g_seed) ^ (fnv1a(key) * 0x100000001B3ULL));
}
uint64_t spoof_field_n(const char *key, unsigned i)
{
char k[96];
snprintf(k, sizeof(k), "%s#%u", key, i);
return spoof_field(k);
}
const char *spoof_pick(const char *key, const char *const *arr, size_t n)
{
return arr[spoof_field(key) % n];
}
void spoof_gen(const char *key, const char *cs, int len, char *out)
{
size_t m = strlen(cs);
for (int i = 0; i < len; i++) {
out[i] = cs[spoof_field_n(key, (unsigned)i) % m];
}
out[len] = '\0';
}
/* one cpu-vendor draw anchors BOTH the CPU signature and the chipset/PCI vendor. */
int spoof_anchor_vendor(void)
{
return (int)(spoof_field("anchor.cpu") & 1); /* 0 Intel, 1 AMD */
}
/* ---- policy knobs --------------------------------------------------------- */
SpoofHvMode spoof_hv_mode(void)
{
config_load();
if (!strcmp(g_hv, "hidden")) return SPOOF_HV_HIDDEN;
if (!strcmp(g_hv, "hyperv")) return SPOOF_HV_HYPERV;
if (!strcmp(g_hv, "off")) return SPOOF_HV_OFF;
return g_enabled ? SPOOF_HV_HYPERV : SPOOF_HV_OFF; /* seeded default = hyperv */
}
bool spoof_waet_drop(void)
{
config_load();
if (!strcmp(g_waet, "on")) return true;
if (!strcmp(g_waet, "off")) return false;
return g_enabled; /* seeded default = drop */
}
bool spoof_pvpanic_hide(void)
{
config_load();
if (!strcmp(g_pvp, "on")) return true;
if (!strcmp(g_pvp, "off")) return false;
return g_enabled; /* seeded default = hide pvpanic */
}
SpoofVgidPolicy spoof_vmgenid_policy(void)
{
config_load();
if (!strcmp(g_vgid, "hide")) return SPOOF_VGID_HIDE;
if (!strcmp(g_vgid, "mask")) return SPOOF_VGID_MASK;
if (!strcmp(g_vgid, "keep")) return SPOOF_VGID_KEEP;
switch (spoof_hv_mode()) { /* default tracks hv mode */
case SPOOF_HV_HIDDEN: return SPOOF_VGID_HIDE;
case SPOOF_HV_HYPERV: return SPOOF_VGID_MASK;
default: return SPOOF_VGID_KEEP;
}
}
+41
View File
@@ -0,0 +1,41 @@
/*
* spoof-core.h — the engine shared by every spoof-<aspect>.c (INTERNAL).
*
* Pure, seed-driven derivation: no entropy, no files, deterministic. Aspect
* modules use these to pick from pools or generate format-correct values, and
* cache their own result (lazily) for stable storage.
*/
#ifndef QEMU_SPOOF_CORE_H
#define QEMU_SPOOF_CORE_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif
bool spoof_on(void); /* seed present */
uint64_t spoof_field(const char *key); /* stable 64-bit draw for a key */
uint64_t spoof_field_n(const char *key, unsigned i); /* nth independent draw */
const char *spoof_pick(const char *key, const char *const *arr, size_t n);
void spoof_gen(const char *key, const char *cs, int len, char *out); /* len chars from cs */
int spoof_anchor_vendor(void); /* 0=Intel, 1=AMD (stable per seed) */
/* platform anchor (spoof-platform.c): one coherent machine identity per seed. */
const char *spoof_plat_acpi_oem(void); /* 6-byte ACPI OEM id */
const char *spoof_plat_acpi_table(void); /* 8-byte ACPI OEM table id */
const char *spoof_plat_machine_desc(void); /* board model (CPU-vendor matched) */
const char *spoof_plat_socket(void); /* that board's CPU socket */
const char *spoof_plat_bios_vendor(void);
const char *spoof_plat_baseboard(void);
const char *spoof_plat_oem_string(void); /* SMBIOS type11 */
extern const char SPOOF_A36[37]; /* "0-9A-Z" + NUL */
extern const char SPOOF_LET[27]; /* "A-Z" + NUL */
extern const char SPOOF_DIG[11]; /* "0-9" + NUL */
#define SPOOF_PICK(key, arr) spoof_pick((key), (arr), ARRAY_SIZE(arr))
#endif /* QEMU_SPOOF_CORE_H */
+44
View File
@@ -0,0 +1,44 @@
/* spoof-cpu.c — CPUID hypervisor signature, anchored to the CPU vendor. */
#include "qemu/osdep.h"
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
/* index 0 = Intel, 1 = AMD (the anchor). The chipset/PCI vendor tracks this. */
static const char *const KVM_SIG[] = { "GenuineIntel", "AuthenticAMD" };
const char *spoof_kvm_signature(const char *def)
{
return spoof_on() ? KVM_SIG[spoof_anchor_vendor()] : def;
}
/* CPUID leaf 0x16 (Processor Frequency Information) — QEMU returns zeros; real
* CPUs report base/max/bus MHz. Fill plausible, self-consistent values. */
static const int BASE_MHZ[] = { 2400, 2900, 3000, 3200, 3400, 3600, 3700, 3800 };
static const int TURBO_MHZ[] = { 400, 600, 800, 1000, 1200, 1400 };
int spoof_cpu_base_mhz(int def)
{
return spoof_on() ? BASE_MHZ[spoof_field("cpu.base") % ARRAY_SIZE(BASE_MHZ)] : def;
}
int spoof_cpu_max_mhz(int def)
{
if (!spoof_on()) return def;
return spoof_cpu_base_mhz(0) + TURBO_MHZ[spoof_field("cpu.turbo") % ARRAY_SIZE(TURBO_MHZ)];
}
int spoof_cpu_bus_mhz(int def)
{
return spoof_on() ? 100 : def;
}
/* Microcode revision, returned already positioned for the MSR (IA32_UCODE_REV
* 0x8B): Intel reports the revision in bits 63:32, AMD the patch level in 31:0.
* Stock QEMU/KVM defaults (Intel 0x1_00000000, AMD 0x01000065) follow the same
* layout, so the seeded value stays a drop-in. Anchored to the persona's CPU
* vendor (the operator drives -cpu to match, as with the KVM signature). */
uint64_t spoof_cpu_microcode(uint64_t def)
{
if (!spoof_on()) return def;
if (spoof_anchor_vendor()) { /* AMD: patch level, low dword */
return 0x0A201000ull | (uint64_t)(spoof_field("ucode") & 0xFFF);
}
return ((uint64_t)(0xC0u | (spoof_field("ucode") & 0x3F))) << 32; /* Intel: revision, hi dword */
}
+41
View File
@@ -0,0 +1,41 @@
/* spoof-display.c — EDID: vendor + name from pools; product code / serial /
* manufacture date GENERATED (real per-unit fields). */
#include "qemu/osdep.h"
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
static const char *const EDID_VEND[] = {
"DEL", "SAM", "AUS", "LEN", "HWP", "ACR", "BNQ", "GSM", "AOC", "MSI", "VSC", "PHL",
"GBT", "IVM", "NEC", "EIZ", "SHP", "SNY", "AUO", "CMN", "LGD", "BOE", "HSD", "VIZ",
};
static const char *const EDID_NAME[] = {
"DELL U2415", "DELL P2419H", "DELL S2721DGF", "SyncMaster", "S24F350", "Odyssey G5",
"ASUS VG248", "VG279Q", "ProArt PA248", "LG 27GL850", "27GP850", "UltraGear",
"BenQ GL2480", "HP 24mh", "HP X27q", "ThinkVision T24i", "Acer KG241", "Nitro VG240",
"AOC 24G2", "ViewSonic VX2458", "Philips 245V", "iiyama PL2480H", "EIZO EV2456", "MSI G241",
};
const char *spoof_edid_vendor(const char *def)
{
return spoof_on() ? SPOOF_PICK("edid.vend", EDID_VEND) : def;
}
const char *spoof_edid_name(const char *def)
{
return spoof_on() ? SPOOF_PICK("edid.name", EDID_NAME) : def;
}
uint16_t spoof_edid_model(uint16_t def)
{
return spoof_on() ? (uint16_t)(spoof_field("edid.model") % 0xFFFE) + 1 : def;
}
uint32_t spoof_edid_serial(uint32_t def)
{
return spoof_on() ? (uint32_t)spoof_field("edid.serial") : def;
}
int spoof_edid_year(int def)
{
return spoof_on() ? 2017 + (int)(spoof_field("edid.year") % 8) : def; /* 2017..2024 */
}
int spoof_edid_week(int def)
{
return spoof_on() ? 1 + (int)(spoof_field("edid.week") % 52) : def;
}
+32
View File
@@ -0,0 +1,32 @@
/* spoof-fwcfg.c — fw_cfg signatures + ACPI _HID (the "QEMU"/"QEMU CFG" tells). */
#include "qemu/osdep.h"
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
static const char *const FWCFG_SIG[] = {
"INTL", "ASUS", "DELL", "LENV", "HPQ ", "MSI ", "ACER", "GBT ", "ASRK", "SUPM", "BIOS", "SONY",
};
static const char *const FWCFG_DEVID[] = {
"INTC0002", "PNP0C01", "PNP0C02", "ASUS0002", "DELL0002", "LEN0078", "HPQ0002",
"MSFT0002", "ACPI0002", "INT33A0",
};
const char *spoof_fwcfg_sig(const char *def)
{
return spoof_on() ? SPOOF_PICK("fwcfg.sig", FWCFG_SIG) : def;
}
const char *spoof_fwcfg_acpi_devid(const char *def)
{
return spoof_on() ? SPOOF_PICK("fwcfg.devid", FWCFG_DEVID) : def;
}
uint64_t spoof_fwcfg_dma_sig(uint64_t def)
{
if (!spoof_on()) {
return def;
}
uint64_t v = 0;
for (int i = 0; i < 8; i++) {
v = (v << 8) | (uint8_t)SPOOF_A36[spoof_field_n("fwcfg.dma", (unsigned)i) % (sizeof(SPOOF_A36) - 1)];
}
return v;
}
+10
View File
@@ -0,0 +1,10 @@
/* spoof-machine.c — the QEMU machine `desc` string. Comes from the PLATFORM
* anchor (spoof-platform.c) so the model matches the ACPI OEM / BIOS / baseboard. */
#include "qemu/osdep.h"
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
const char *spoof_machine_desc(const char *def)
{
return spoof_on() ? spoof_plat_machine_desc() : def;
}
+38
View File
@@ -0,0 +1,38 @@
/* spoof-pci.c — PCI identity: hide the Red Hat/virtio fingerprint, anchored to
* the CPU vendor so the chipset matches the CPU (Intel CPU -> Intel chipset). */
#include "qemu/osdep.h"
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
static const uint16_t PCI_VENDOR[] = { 0x8086 /* Intel */, 0x1022 /* AMD */ };
uint16_t spoof_pci_vendor(uint16_t def)
{
return spoof_on() ? PCI_VENDOR[spoof_anchor_vendor()] : def;
}
uint16_t spoof_pci_subvendor(uint16_t def)
{
return spoof_on() ? PCI_VENDOR[spoof_anchor_vendor()] : def;
}
uint16_t spoof_pci_device(const char *role, uint16_t def)
{
if (!spoof_on()) {
return def;
}
/* TODO: curated real per-role device-id table. For now keep it stable per
* role and in a plausible range (never the bogus device==vendor case). */
char k[64];
snprintf(k, sizeof(k), "pci.dev.%s", role ? role : "");
return (uint16_t)(0x1500 + (spoof_field(k) % 0x0400));
}
/* Subsystem device id (the OEM/board sub-identity); subsystem vendor =
* spoof_pci_subvendor. Gives QEMU-origin controllers an OEM-branded SSID. */
uint16_t spoof_pci_subdevice(const char *role, uint16_t def)
{
if (!spoof_on()) {
return def;
}
char k[64];
snprintf(k, sizeof(k), "pci.ssid.%s", role ? role : "");
return (uint16_t)(0x8000 | (spoof_field(k) & 0x7FFF));
}
+95
View File
@@ -0,0 +1,95 @@
/*
* spoof-platform.c — the PLATFORM anchor: one seed draw selects a coherent
* machine identity (ACPI OEM id, ACPI table id, BIOS vendor, baseboard, the
* board model AND its socket). The board list is split by CPU vendor and indexed
* by the CPU anchor, so the board's chipset/socket always matches the CPU
* (no "Intel H510 board + AMD CPU"). The board's own socket feeds SMBIOS type4.
*
* Two classes: OEM prebuilt (ACPI OEM = vendor code) and DIY/retail (AMI Aptio
* firmware -> ACPI OEM "ALASKA"/"A M I"; the board brand is in the baseboard).
*/
#include "qemu/osdep.h"
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
typedef struct { const char *model, *socket; } Board;
/* --- OEM prebuilt: real Intel and (separate) AMD desktop SKUs --- */
static const Board DELL_I[] = { {"OptiPlex 7090","LGA1200"}, {"OptiPlex 5090","LGA1200"},
{"Precision 3650 Tower","LGA1200"}, {"OptiPlex 7000","LGA1700"} };
static const Board DELL_A[] = { {"OptiPlex 5055","AM4"}, {"OptiPlex 7010 AMD","AM4"} };
static const Board HP_I[] = { {"HP EliteDesk 800 G6","LGA1200"}, {"HP ProDesk 600 G6","LGA1200"},
{"HP Z2 Tower G5","LGA1200"}, {"HP EliteDesk 800 G9","LGA1700"} };
static const Board HP_A[] = { {"HP ProDesk 405 G6","AM4"}, {"HP ProDesk 485 G4","AM4"} };
static const Board LEN_I[] = { {"ThinkCentre M720q","LGA1151"}, {"ThinkCentre M920t","LGA1151"},
{"ThinkStation P340","LGA1200"}, {"ThinkCentre M70q Gen 3","LGA1700"} };
static const Board LEN_A[] = { {"ThinkCentre M75q Gen 2","AM4"}, {"ThinkCentre M75s Gen 2","AM4"} };
static const Board ACE_I[] = { {"Veriton M4660G","LGA1151"}, {"Aspire TC-895","LGA1200"},
{"Aspire TC-1760","LGA1700"} };
static const Board ACE_A[] = { {"Nitro N50-110","AM4"}, {"Aspire TC-380","AM4"} };
/* --- DIY/retail boards: the model unambiguously implies the socket --- */
static const Board ASUS_I[] = { {"ROG STRIX Z590-E GAMING WIFI","LGA1200"}, {"PRIME B560M-A","LGA1200"},
{"TUF GAMING Z690-PLUS WIFI","LGA1700"}, {"PRIME H610M-E D4","LGA1700"} };
static const Board ASUS_A[] = { {"TUF GAMING B550M-PLUS","AM4"}, {"ROG STRIX X570-E GAMING","AM4"},
{"PRIME B650M-A","AM5"}, {"ProArt X670E-CREATOR WIFI","AM5"} };
static const Board GBT_I[] = { {"Z390 AORUS PRO","LGA1151"}, {"B660M DS3H DDR4","LGA1700"},
{"Z690 AORUS ELITE AX","LGA1700"} };
static const Board GBT_A[] = { {"X570 AORUS ELITE","AM4"}, {"B450 AORUS M","AM4"}, {"B650 AORUS ELITE AX","AM5"} };
static const Board MSI_I[] = { {"MAG B560 TOMAHAWK","LGA1200"}, {"PRO B660M-A DDR4","LGA1700"},
{"MPG Z690 CARBON WIFI","LGA1700"} };
static const Board MSI_A[] = { {"MPG B550 GAMING PLUS","AM4"}, {"B450 TOMAHAWK MAX","AM4"},
{"MAG B650 TOMAHAWK WIFI","AM5"} };
static const Board ASR_I[] = { {"H510M-HVS","LGA1200"}, {"B660M Pro RS","LGA1700"},
{"Z690 Steel Legend","LGA1700"} };
static const Board ASR_A[] = { {"X570 Taichi","AM4"}, {"B450M Steel Legend","AM4"}, {"B650M PG Riptide","AM5"} };
typedef struct {
const char *oem, *table, *bios, *board, *oemstr;
const Board *intel; size_t ni;
const Board *amd; size_t na;
} Platform;
#define BL(a) (a), ARRAY_SIZE(a)
static const Platform PLAT[] = {
/* --- OEM prebuilt: ACPI OEM id = vendor code --- */
{ "DELL ", "QA09 ", "Dell Inc.", "Dell Inc.", "Dell System", BL(DELL_I), BL(DELL_A) },
{ "HPQOEM", "8054 ", "HP", "HP", "HP", BL(HP_I), BL(HP_A) },
{ "LENOVO", "TP-N1Q ", "LENOVO", "LENOVO", "LENOVO", BL(LEN_I), BL(LEN_A) },
{ "ACRSYS", "ACRSYS ", "Insyde Corp.", "Acer", "Acer System", BL(ACE_I), BL(ACE_A) },
/* --- DIY/retail (AMI firmware): ACPI OEM id "ALASKA"; brand in baseboard --- */
{ "ALASKA", "A M I ", "American Megatrends Inc.", "ASUSTeK COMPUTER INC.", "$ASUS$", BL(ASUS_I), BL(ASUS_A) },
{ "ALASKA", "A M I ", "American Megatrends Inc.", "Gigabyte Technology Co., Ltd.", "GIGABYTE", BL(GBT_I), BL(GBT_A) },
{ "ALASKA", "A M I ", "American Megatrends Inc.", "Micro-Star International Co., Ltd.", "MSI", BL(MSI_I), BL(MSI_A) },
{ "ALASKA", "A M I ", "American Megatrends Inc.", "ASRock", "ASRock", BL(ASR_I), BL(ASR_A) },
};
static const Platform *plat(void)
{
return &PLAT[spoof_field("anchor.platform") % ARRAY_SIZE(PLAT)];
}
/* the chosen board for THIS platform under the CPU-vendor anchor (Intel/AMD). */
static const Board *chosen_board(void)
{
const Platform *p = plat();
const Board *list = spoof_anchor_vendor() ? p->amd : p->intel;
size_t n = spoof_anchor_vendor() ? p->na : p->ni;
return &list[spoof_field("mach.board") % n];
}
const char *spoof_plat_acpi_oem(void) { return plat()->oem; }
const char *spoof_plat_acpi_table(void) { return plat()->table; }
const char *spoof_plat_bios_vendor(void) { return plat()->bios; }
const char *spoof_plat_baseboard(void) { return plat()->board; }
const char *spoof_plat_machine_desc(void) { return chosen_board()->model; }
const char *spoof_plat_socket(void) { return chosen_board()->socket; }
const char *spoof_plat_oem_string(void) { return plat()->oemstr; }
const char *spoof_bios_vendor(const char *def)
{
return spoof_on() ? spoof_plat_bios_vendor() : def;
}
const char *spoof_baseboard_manufacturer(const char *def)
{
return spoof_on() ? spoof_plat_baseboard() : def;
}
+82
View File
@@ -0,0 +1,82 @@
/* spoof-smbios.c — SMBIOS: clear the type0 "VM" bit, fill type17 (memory) and
* type4 (processor). CPU manufacturer + socket are anchored to the CPU vendor. */
#include "qemu/osdep.h"
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
static const char *const MEM_MFR[] = {
"Samsung", "SK Hynix", "Micron Technology", "Crucial", "Corsair",
"G.Skill", "Kingston", "ADATA", "Patriot", "Team Group",
};
static const char *const CPU_MFR[] = { "Intel(R) Corporation", "Advanced Micro Devices, Inc." };
static const char *mem_prefix(const char *mfr)
{
if (!strncmp(mfr, "Samsung", 7)) return "M";
if (!strncmp(mfr, "SK", 2)) return "HMA";
if (!strncmp(mfr, "Micron", 6)) return "MTA";
if (!strncmp(mfr, "Crucial", 7)) return "CT";
if (!strncmp(mfr, "Corsair", 7)) return "CM";
if (!strncmp(mfr, "G.Skill", 7)) return "F4-";
if (!strncmp(mfr, "Kingston", 8)) return "K";
return "AD";
}
static struct {
const char *mfr, *part_pre;
char part[24], serial[12];
bool done;
} m;
static void m_init(void)
{
if (m.done) {
return;
}
m.mfr = SPOOF_PICK("mem.mfr", MEM_MFR);
char tail[16];
spoof_gen("mem.part", SPOOF_A36, 12, tail);
snprintf(m.part, sizeof(m.part), "%s%s", mem_prefix(m.mfr), tail);
spoof_gen("mem.serial", SPOOF_A36, 8, m.serial);
m.done = true;
}
bool spoof_smbios_hide_vm(void) { return spoof_on(); }
const char *spoof_smbios_mem_manufacturer(const char *def)
{
if (!spoof_on()) return def;
m_init(); return m.mfr;
}
const char *spoof_smbios_mem_part(const char *def)
{
if (!spoof_on()) return def;
m_init(); return m.part;
}
const char *spoof_smbios_mem_serial(const char *def)
{
if (!spoof_on()) return def;
m_init(); return m.serial;
}
const char *spoof_smbios_cpu_manufacturer(const char *def)
{
return spoof_on() ? CPU_MFR[spoof_anchor_vendor()] : def;
}
const char *spoof_smbios_cpu_socket(const char *def)
{
/* the socket of the platform's chosen board -> matches the model + CPU vendor. */
return spoof_on() ? spoof_plat_socket() : def;
}
const char *spoof_smbios_oem_string(const char *def) /* type11 */
{
return spoof_on() ? spoof_plat_oem_string() : def;
}
int spoof_smbios_chassis_type(int def) /* type3: 3=Desktop, 10=Notebook */
{
if (!spoof_on()) return def;
const char *m = spoof_plat_machine_desc();
if (strstr(m, "ThinkPad") || strstr(m, "Latitude") || strstr(m, "EliteBook")) {
return 10;
}
return 3; /* all current models are desktops */
}
+141
View File
@@ -0,0 +1,141 @@
/* spoof-storage.c — disks/NVMe/CD-ROM. Model = pool; per-unit serial / WWN /
* firmware / EUI64 / NGUID = GENERATED to brand-correct format. */
#include "qemu/osdep.h"
#include "qemu/cutils.h" /* pstrcpy */
#include "hw/misc/spoof.h"
#include "hw/misc/spoof-core.h"
/* rpm: 1 = SSD (non-rotational), else the spinning rate — stored, not guessed. */
typedef struct { const char *model; int rpm; } DiskModel;
static const DiskModel DISK_MODEL[] = {
{ "Samsung SSD 870 EVO 500GB", 1 }, { "Samsung SSD 860 EVO 1TB", 1 }, { "Samsung SSD 870 QVO 2TB", 1 },
{ "WDC WD10EZEX-08WN4A0", 7200 }, { "WDC WD20EZBX-00AYRA0", 5400 }, { "WDC WDS500G2B0A-00SM50", 1 },
{ "ST1000DM010-2EP102", 7200 }, { "ST2000DM008-2FR102", 7200 }, { "ST500DM002-1BD142", 7200 },
{ "CT500MX500SSD1", 1 }, { "CT1000BX500SSD1", 1 }, { "KINGSTON SA400S37480G", 1 }, { "KINGSTON SKC600512G", 1 },
{ "Crucial_CT525MX300SSD1", 1 }, { "TOSHIBA DT01ACA100", 7200 }, { "SanDisk SDSSDA240G", 1 },
{ "INTEL SSDSC2KW256G8", 1 }, { "HGST HUS726T4TALA6L4", 7200 }, { "ADATA SU800", 1 }, { "PNY CS900 240GB", 1 },
};
static const char *const NVME_MODEL[] = {
"Samsung SSD 980 1TB", "Samsung SSD 970 EVO Plus 1TB", "Samsung SSD 990 PRO 2TB",
"WD_BLACK SN770 1TB", "WD Blue SN570 1TB", "KXG60ZNV512G TOSHIBA",
"CT1000P3SSD8", "CT500P5SSD8", "Sabrent Rocket 4.0", "KINGSTON SNV2S1000G",
"ADATA SX8200PNP", "Seagate FireCuda 520", "Corsair MP600", "PNY CS3030",
"INTEL SSDPEKNW010T8", "SKHynix_HFS512GD9TNG", "Micron 2210 MTFDHBA512QFD", "GIGABYTE GP-GSM2NE3",
};
static const char *const CDROM_MODEL[] = {
"HL-DT-ST DVD-RAM GH24NSD1", "ASUS DRW-24D5MT", "TSSTcorp CDDVDW SH-224",
"PLDS DVD+-RW DH-16AES", "HL-DT-ST DVDRWBD CH12NS30",
};
/* Disk serial in the format the model's brand actually uses. */
static void gen_disk_serial(const char *model, char *out, size_t n)
{
char b[24];
if (!strncmp(model, "Samsung", 7)) { /* S<3L>N<d>0<6 alnum> */
b[0] = 'S';
for (int i = 0; i < 3; i++) b[1 + i] = SPOOF_LET[spoof_field_n("dsk.sa.l", i) % 26];
b[4] = 'N'; b[5] = SPOOF_DIG[spoof_field_n("dsk.sa.d", 0) % 10]; b[6] = '0';
for (int i = 0; i < 6; i++) b[7 + i] = SPOOF_A36[spoof_field_n("dsk.sa.t", i) % 36];
b[13] = '\0';
} else if (!strncmp(model, "WDC", 3) || !strncmp(model, "WD", 2)) { /* WD-WCC<7> */
memcpy(b, "WD-WCC", 6);
for (int i = 0; i < 7; i++) b[6 + i] = SPOOF_A36[spoof_field_n("dsk.wd", i) % 36];
b[13] = '\0';
} else if (!strncmp(model, "ST", 2)) { /* Seagate: Z<7 base36> */
b[0] = 'Z';
for (int i = 0; i < 7; i++) b[1 + i] = SPOOF_A36[spoof_field_n("dsk.st", i) % 36];
b[8] = '\0';
} else if (!strncmp(model, "CT", 2) || !strncmp(model, "Crucial", 7)) { /* 4d+12 */
for (int i = 0; i < 4; i++) b[i] = SPOOF_DIG[spoof_field_n("dsk.ct.a", i) % 10];
for (int i = 0; i < 12; i++) b[4 + i] = SPOOF_A36[spoof_field_n("dsk.ct.b", i) % 36];
b[16] = '\0';
} else { /* generic 16 alnum */
for (int i = 0; i < 16; i++) b[i] = SPOOF_A36[spoof_field_n("dsk.g", i) % 36];
b[16] = '\0';
}
pstrcpy(out, n, b);
}
static void gen_nvme_serial(const char *model, char *out, size_t n)
{
char b[24];
if (!strncmp(model, "Samsung", 7)) {
b[0] = 'S';
for (int i = 0; i < 3; i++) b[1 + i] = SPOOF_LET[spoof_field_n("nvm.sa.l", i) % 26];
memcpy(b + 4, "NX0", 3);
for (int i = 0; i < 7; i++) b[7 + i] = SPOOF_A36[spoof_field_n("nvm.sa.t", i) % 36];
b[14] = '\0';
} else {
for (int i = 0; i < 20; i++) b[i] = SPOOF_A36[spoof_field_n("nvm.g", i) % 36];
b[20] = '\0';
}
pstrcpy(out, n, b);
}
/* WWN: NAA-5 + a brand-matched 24-bit OUI + 36-bit vendor-specific. */
static uint64_t brand_oui(const char *m)
{
if (!strncmp(m, "Samsung", 7)) return 0x002538;
if (!strncmp(m, "WD", 2)) return 0x0014EE;
if (!strncmp(m, "ST", 2)) return 0x000C50;
if (!strncmp(m, "CT", 2) || !strncmp(m, "Crucial", 7)) return 0x00A075; /* Micron */
return 0x0024E9;
}
static struct {
const char *model; char serial[24], fw[12]; uint64_t wwn; int rpm;
bool done;
} d;
static struct {
const char *model; char serial[24], fw[12]; uint64_t eui64;
bool done;
} v;
static void d_init(void)
{
if (d.done) return;
const DiskModel *dm = &DISK_MODEL[spoof_field("disk.model") % ARRAY_SIZE(DISK_MODEL)];
d.model = dm->model;
d.rpm = dm->rpm;
gen_disk_serial(d.model, d.serial, sizeof(d.serial));
spoof_gen("disk.fw", SPOOF_A36, 8, d.fw);
d.wwn = (0x5ULL << 60) | (brand_oui(d.model) << 36) | (spoof_field("disk.wwn") & 0xFFFFFFFFFULL);
d.done = true;
}
static void v_init(void)
{
if (v.done) return;
v.model = SPOOF_PICK("nvme.model", NVME_MODEL);
gen_nvme_serial(v.model, v.serial, sizeof(v.serial));
spoof_gen("nvme.fw", SPOOF_A36, 8, v.fw);
v.eui64 = (brand_oui(v.model) << 40) | (spoof_field("nvme.eui") & 0xFFFFFFFFFFULL);
v.done = true;
}
const char *spoof_disk_model(const char *def) { if (!spoof_on()) return def; d_init(); return d.model; }
const char *spoof_disk_serial(const char *def) { if (!spoof_on()) return def; d_init(); return d.serial; }
const char *spoof_disk_fw(const char *def) { if (!spoof_on()) return def; d_init(); return d.fw; }
uint64_t spoof_disk_wwn(uint64_t def) { if (!spoof_on()) return def; d_init(); return d.wwn; }
const char *spoof_nvme_model(const char *def) { if (!spoof_on()) return def; v_init(); return v.model; }
const char *spoof_nvme_serial(const char *def) { if (!spoof_on()) return def; v_init(); return v.serial; }
const char *spoof_nvme_fw(const char *def) { if (!spoof_on()) return def; v_init(); return v.fw; }
uint64_t spoof_nvme_eui64(uint64_t def) { if (!spoof_on()) return def; v_init(); return v.eui64; }
void spoof_nvme_nguid(uint8_t out[16])
{
if (!spoof_on()) return;
for (int i = 0; i < 16; i++) {
out[i] = (uint8_t)spoof_field_n("nvme.nguid", (unsigned)i);
}
}
const char *spoof_cdrom_model(const char *def)
{
return spoof_on() ? SPOOF_PICK("cdrom.model", CDROM_MODEL) : def;
}
/* ATA rotation rate: 1 = non-rotational (SSD), else RPM — matched to the model
* so an "SSD 870 EVO" reports SSD and an "ST1000DM" reports a spinning rate. */
int spoof_disk_rotation(int def)
{
if (!spoof_on()) return def;
d_init();
return d.rpm; /* 1 = SSD, else RPM (from the pool) */
}
+103
View File
@@ -0,0 +1,103 @@
/*
* qemu-spoof — seed-driven, per-VM hardware identity for anti-detection.
*
* PUBLIC header: this is what QEMU call sites include. Every getter takes the
* STOCK default and returns it verbatim when spoofing is disabled (no seed) — so
* an un-seeded VM behaves exactly like stock QEMU (safe fallback). With a seed,
* each value is derived deterministically: same seed -> same coherent persona,
* different seed -> different one (kills fleet correlation).
*
* Implementation is decomposed by aspect (spoof-core.c + spoof-<aspect>.c).
*
* Inputs (env var = test fallback for the machine property):
* spoof-seed / QEMU_SPOOF_SEED persona seed (empty => everything stock)
* spoof-hv / QEMU_SPOOF_HV hv mode: off|hyperv|hidden
* spoof-waet / QEMU_SPOOF_WAET drop the WAET ACPI table: on|off
* spoof-vmgenid/ QEMU_SPOOF_VMGENID vmgenid: keep|mask|hide
* Config (the machine property) ALWAYS overrides the seed-implied default.
*/
#ifndef QEMU_SPOOF_H
#define QEMU_SPOOF_H
#include <stdint.h>
#include <stdbool.h>
bool spoof_enabled(void); /* a seed was supplied */
/* ---- policy knobs (config-overridable; not part of the random persona) ----- */
typedef enum { SPOOF_HV_OFF = 0, SPOOF_HV_HYPERV, SPOOF_HV_HIDDEN } SpoofHvMode;
SpoofHvMode spoof_hv_mode(void); /* seeded default = HYPERV, else OFF */
typedef enum { SPOOF_VGID_KEEP = 0, SPOOF_VGID_MASK, SPOOF_VGID_HIDE } SpoofVgidPolicy;
SpoofVgidPolicy spoof_vmgenid_policy(void); /* HIDDEN->HIDE, HYPERV->MASK, OFF->KEEP */
bool spoof_waet_drop(void); /* drop the WAET (emulated-devices) table */
bool spoof_pvpanic_hide(void); /* drop the pvpanic device (_HID QEMU0001) */
/* ---- platform (spoof-platform.c) — one coherent firmware/board identity ----
* The ACPI OEM id, machine desc, BIOS vendor and baseboard all come from a single
* per-seed platform pick (OEM-prebuilt vendor code, or "ALASKA"/AMI for DIY). */
const char *spoof_bios_vendor(const char *def); /* SMBIOS type0 */
const char *spoof_baseboard_manufacturer(const char *def); /* SMBIOS type2 */
/* ---- ACPI (spoof-acpi.c; hw/acpi, hw/i386/acpi-build) --------------------- */
const char *spoof_acpi_oem_id(const char *def); /* 6 bytes (platform-anchored) */
const char *spoof_acpi_oem_table_id(const char *def); /* 8 bytes (platform-anchored) */
const char *spoof_acpi_creator_id(const char *def); /* 4 bytes (ASL compiler) */
/* ---- CPU (spoof-cpu.c; target/i386) --------------------------------------- */
const char *spoof_kvm_signature(const char *def); /* 12 bytes; vendor-anchored */
int spoof_cpu_base_mhz(int def); /* CPUID leaf 0x16 base freq */
int spoof_cpu_max_mhz(int def); /* CPUID leaf 0x16 max freq */
int spoof_cpu_bus_mhz(int def); /* CPUID leaf 0x16 bus freq */
uint64_t spoof_cpu_microcode(uint64_t def); /* IA32_UCODE_REV, vendor-positioned */
/* ---- PCI (spoof-pci.c) ---------------------------------------------------- */
uint16_t spoof_pci_vendor(uint16_t def); /* anchored to the CPU vendor */
uint16_t spoof_pci_subvendor(uint16_t def);
uint16_t spoof_pci_device(const char *role, uint16_t def);
uint16_t spoof_pci_subdevice(const char *role, uint16_t def); /* OEM-branded SSID */
/* ---- fw_cfg (spoof-fwcfg.c; hw/nvram/fw_cfg, standard-headers) ------------- */
const char *spoof_fwcfg_sig(const char *def); /* 4-byte selector signature */
uint64_t spoof_fwcfg_dma_sig(uint64_t def); /* 8-byte DMA signature */
const char *spoof_fwcfg_acpi_devid(const char *def); /* fw_cfg device _HID */
/* ---- SMBIOS (spoof-smbios.c; hw/smbios) ----------------------------------- */
bool spoof_smbios_hide_vm(void); /* clear type0 "VM" char. bit */
const char *spoof_smbios_mem_manufacturer(const char *def); /* type17 */
const char *spoof_smbios_mem_part(const char *def);
const char *spoof_smbios_mem_serial(const char *def);
const char *spoof_smbios_cpu_manufacturer(const char *def); /* type4 */
const char *spoof_smbios_cpu_socket(const char *def);
const char *spoof_smbios_oem_string(const char *def); /* type11 */
int spoof_smbios_chassis_type(int def); /* type3: 3=Desktop, 10=Notebook */
/* ---- display / EDID (spoof-display.c; hw/display/edid-generate) ------------ */
const char *spoof_edid_vendor(const char *def); /* 3-char PNP (pool) */
const char *spoof_edid_name(const char *def); /* model name (pool) */
uint16_t spoof_edid_model(uint16_t def); /* product code (generated) */
uint32_t spoof_edid_serial(uint32_t def); /* serial (generated) */
int spoof_edid_year(int def); /* year of manufacture */
int spoof_edid_week(int def); /* week of manufacture */
/* ---- storage (spoof-storage.c; hw/ide, hw/scsi, hw/nvme) ------------------ */
/* model = pool (finite real set); per-unit serial/WWN/EUI64/FW = GENERATED. */
const char *spoof_disk_model(const char *def);
const char *spoof_disk_serial(const char *def); /* brand-matched format */
uint64_t spoof_disk_wwn(uint64_t def); /* NAA-5 + brand OUI */
const char *spoof_disk_fw(const char *def); /* ATA firmware revision */
const char *spoof_nvme_model(const char *def);
const char *spoof_nvme_serial(const char *def);
const char *spoof_nvme_fw(const char *def); /* 8-char rev (fixes FR leak) */
uint64_t spoof_nvme_eui64(uint64_t def); /* OUI + vendor */
void spoof_nvme_nguid(uint8_t out[16]); /* filled iff enabled */
const char *spoof_cdrom_model(const char *def);
int spoof_disk_rotation(int def); /* 1=SSD, else RPM (matches model) */
/* ---- audio (spoof-audio.c; hw/audio/hda-codec) ---------------------------- */
uint32_t spoof_hda_vendor_id(uint32_t def); /* HDA codec vendor/device id */
const char *spoof_hda_name(const char *def);
/* ---- machine (spoof-machine.c; hw/i386/pc_*) ------------------------------ */
const char *spoof_machine_desc(const char *def);
#endif /* QEMU_SPOOF_H */