commit 06463ee65c996243522c463dea80093034bc1330 Author: Gregory Lirent Date: Thu Jun 11 17:34:09 2026 +0300 qemu-spoof: seed-driven per-VM hardware-identity anti-detection for pve-qemu diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2b09307 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto eol=lf +*.patch text eol=lf +*.deb binary diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ee0f2dd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "pve-qemu"] + path = pve-qemu + url = https://git.proxmox.com/git/pve-qemu.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9011b34 --- /dev/null +++ b/Makefile @@ -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* diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1e7525 --- /dev/null +++ b/README.md @@ -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 ,...,spoof-seed= # preferred (Proxmox: via args) +QEMU_SPOOF_SEED= # 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:+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 +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. diff --git a/patches/0002-x86-machine-spoof-properties.patch b/patches/0002-x86-machine-spoof-properties.patch new file mode 100644 index 0000000..cf1f3f4 --- /dev/null +++ b/patches/0002-x86-machine-spoof-properties.patch @@ -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. diff --git a/patches/0010-acpi-table-header.patch b/patches/0010-acpi-table-header.patch new file mode 100644 index 0000000..ab4e4bf --- /dev/null +++ b/patches/0010-acpi-table-header.patch @@ -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 + #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); diff --git a/patches/0011-acpi-policy-waet-vmgenid.patch b/patches/0011-acpi-policy-waet-vmgenid.patch new file mode 100644 index 0000000..f10b15e --- /dev/null +++ b/patches/0011-acpi-policy-waet-vmgenid.patch @@ -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)) { diff --git a/patches/0012-acpi-device-hids.patch b/patches/0012-acpi-device-hids.patch new file mode 100644 index 0000000..7314e31 --- /dev/null +++ b/patches/0012-acpi-device-hids.patch @@ -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))); diff --git a/patches/0013-cpuid-kvm-sig-freq.patch b/patches/0013-cpuid-kvm-sig-freq.patch new file mode 100644 index 0000000..97dda87 --- /dev/null +++ b/patches/0013-cpuid-kvm-sig-freq.patch @@ -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; diff --git a/patches/0014-fwcfg-signatures.patch b/patches/0014-fwcfg-signatures.patch new file mode 100644 index 0000000..c69e15c --- /dev/null +++ b/patches/0014-fwcfg-signatures.patch @@ -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)); diff --git a/patches/0015-smbios-vm-bit.patch b/patches/0015-smbios-vm-bit.patch new file mode 100644 index 0000000..68ab638 --- /dev/null +++ b/patches/0015-smbios-vm-bit.patch @@ -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 */ + } + diff --git a/patches/0016-edid-monitor.patch b/patches/0016-edid-monitor.patch new file mode 100644 index 0000000..4a4a86a --- /dev/null +++ b/patches/0016-edid-monitor.patch @@ -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; diff --git a/patches/0017-storage-identity.patch b/patches/0017-storage-identity.patch new file mode 100644 index 0000000..f2388c0 --- /dev/null +++ b/patches/0017-storage-identity.patch @@ -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); + diff --git a/patches/0018-pci-subsystem-id.patch b/patches/0018-pci-subsystem-id.patch new file mode 100644 index 0000000..45c2c73 --- /dev/null +++ b/patches/0018-pci-subsystem-id.patch @@ -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); diff --git a/patches/0019-machine-desc.patch b/patches/0019-machine-desc.patch new file mode 100644 index 0000000..9f0caf8 --- /dev/null +++ b/patches/0019-machine-desc.patch @@ -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"; diff --git a/patches/0021-smbios-identity.patch b/patches/0021-smbios-identity.patch new file mode 100644 index 0000000..ce96c53 --- /dev/null +++ b/patches/0021-smbios-identity.patch @@ -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 */ diff --git a/patches/0022-storage-extra.patch b/patches/0022-storage-extra.patch new file mode 100644 index 0000000..0a92ed6 --- /dev/null +++ b/patches/0022-storage-extra.patch @@ -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); diff --git a/patches/0023-cpu-microcode.patch b/patches/0023-cpu-microcode.patch new file mode 100644 index 0000000..5fcc462 --- /dev/null +++ b/patches/0023-cpu-microcode.patch @@ -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 */ + } + + /* diff --git a/patches/README.md b/patches/README.md new file mode 100644 index 0000000..ae7d40f --- /dev/null +++ b/patches/README.md @@ -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. diff --git a/pve-qemu b/pve-qemu new file mode 160000 index 0000000..817dab1 --- /dev/null +++ b/pve-qemu @@ -0,0 +1 @@ +Subproject commit 817dab1cdae098114aa95c4ba48a6d038a904647 diff --git a/src/spoof-acpi.c b/src/spoof-acpi.c new file mode 100644 index 0000000..bd7b9f1 --- /dev/null +++ b/src/spoof-acpi.c @@ -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; +} diff --git a/src/spoof-audio.c b/src/spoof-audio.c new file mode 100644 index 0000000..64bb556 --- /dev/null +++ b/src/spoof-audio.c @@ -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; +} diff --git a/src/spoof-core.c b/src/spoof-core.c new file mode 100644 index 0000000..217295c --- /dev/null +++ b/src/spoof-core.c @@ -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; + } +} diff --git a/src/spoof-core.h b/src/spoof-core.h new file mode 100644 index 0000000..5294621 --- /dev/null +++ b/src/spoof-core.h @@ -0,0 +1,41 @@ +/* + * spoof-core.h — the engine shared by every spoof-.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 +#include +#include + +#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 */ diff --git a/src/spoof-cpu.c b/src/spoof-cpu.c new file mode 100644 index 0000000..fea9c37 --- /dev/null +++ b/src/spoof-cpu.c @@ -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 */ +} diff --git a/src/spoof-display.c b/src/spoof-display.c new file mode 100644 index 0000000..6b395d2 --- /dev/null +++ b/src/spoof-display.c @@ -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; +} diff --git a/src/spoof-fwcfg.c b/src/spoof-fwcfg.c new file mode 100644 index 0000000..cc5f31e --- /dev/null +++ b/src/spoof-fwcfg.c @@ -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; +} diff --git a/src/spoof-machine.c b/src/spoof-machine.c new file mode 100644 index 0000000..b19af8f --- /dev/null +++ b/src/spoof-machine.c @@ -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; +} diff --git a/src/spoof-pci.c b/src/spoof-pci.c new file mode 100644 index 0000000..82ad9a3 --- /dev/null +++ b/src/spoof-pci.c @@ -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)); +} diff --git a/src/spoof-platform.c b/src/spoof-platform.c new file mode 100644 index 0000000..bac6ebd --- /dev/null +++ b/src/spoof-platform.c @@ -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; +} diff --git a/src/spoof-smbios.c b/src/spoof-smbios.c new file mode 100644 index 0000000..0bcfe86 --- /dev/null +++ b/src/spoof-smbios.c @@ -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 */ +} diff --git a/src/spoof-storage.c b/src/spoof-storage.c new file mode 100644 index 0000000..b92e4bb --- /dev/null +++ b/src/spoof-storage.c @@ -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>N0<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) */ +} diff --git a/src/spoof.h b/src/spoof.h new file mode 100644 index 0000000..1db4dd2 --- /dev/null +++ b/src/spoof.h @@ -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-.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 +#include + +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 */