diff --git a/README.md b/README.md index f1e7525..c0f51f1 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,21 @@ 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. +### Modes + +`-machine ...,spoof-mode=` selects the identity strategy (two axes — hardware +persona × hypervisor presence — as presets): + +| mode | persona | presence | looks like | +|---|---|---|---| +| `none` | stock | KVM | a plain VM (no spoof) | +| `hyperv` | Microsoft "Virtual Machine" | Hyper-V | an honest Hyper-V guest | +| `vbs` (default when seeded) | real OEM | Hyper-V | a physical Win11 box with VBS on | +| `physical` | real OEM | bare metal | a physical machine (no hypervisor) | + +The presence axis (clearing the hypervisor bit / Hyper-V enlightenments) is partly +the CPU model configuration (`cpu: host,hidden=1` + `hv-*`); set it to match the mode. + ## Layout ``` diff --git a/patches/0002-x86-machine-spoof-properties.patch b/patches/0002-x86-machine-spoof-properties.patch index cf1f3f4..40fbf7a 100644 --- a/patches/0002-x86-machine-spoof-properties.patch +++ b/patches/0002-x86-machine-spoof-properties.patch @@ -1,14 +1,15 @@ 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. +Adds string machine properties spoof-seed / spoof-mode / 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-*"). spoof-mode (none|hyperv|vbs| +physical) is the master fork; spoof-hv is the legacy knob. Inert unless set. diff --git a/hw/i386/x86.c b/hw/i386/x86.c -index 01872cb..66400fe 100644 +index 01872cb..702b327 100644 --- a/hw/i386/x86.c +++ b/hw/i386/x86.c -@@ -372,6 +372,26 @@ static void x86_machine_initfn(Object *obj) +@@ -372,6 +372,27 @@ static void x86_machine_initfn(Object *obj) x86ms->above_4g_mem_start = 4 * GiB; } @@ -27,6 +28,7 @@ index 01872cb..66400fe 100644 + x86ms->field = g_strdup(value); \ + } +X86_SPOOF_PROP(spoof_seed) ++X86_SPOOF_PROP(spoof_mode) +X86_SPOOF_PROP(spoof_hv) +X86_SPOOF_PROP(spoof_waet) +X86_SPOOF_PROP(spoof_vmgenid) @@ -35,7 +37,7 @@ index 01872cb..66400fe 100644 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) +@@ -426,6 +447,31 @@ 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"); @@ -43,10 +45,14 @@ index 01872cb..66400fe 100644 + 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-mode", ++ x86_machine_get_spoof_mode, x86_machine_set_spoof_mode); ++ object_class_property_set_description(oc, "spoof-mode", ++ "qemu-spoof: identity mode (none|hyperv|vbs|physical)"); + 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)"); ++ "qemu-spoof: legacy hv knob (off|hyperv|hidden); prefer spoof-mode"); + 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", @@ -64,7 +70,7 @@ index 01872cb..66400fe 100644 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 +index 71fe6b5..c1f5f7e 100644 --- a/include/hw/i386/x86.h +++ b/include/hw/i386/x86.h @@ -79,6 +79,9 @@ struct X86MachineState { @@ -73,7 +79,7 @@ index 71fe6b5..a80700f 100644 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; ++ char *spoof_seed, *spoof_mode, *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/0024-smbios-bios-type0.patch b/patches/0024-smbios-bios-type0.patch index 26c78f2..5a77d19 100644 --- a/patches/0024-smbios-bios-type0.patch +++ b/patches/0024-smbios-bios-type0.patch @@ -1,25 +1,29 @@ -qemu-spoof: SMBIOS type0 BIOS vendor / version / date +qemu-spoof: SMBIOS type0 BIOS + type1 system identity -Stock QEMU leaves type0 vendor/version/date unset, so the guest reads an empty or -firmware-default ("SeaBIOS") BIOS identity via WMI Win32_BIOS / dmidecode -- a tell. -Force a real, platform-anchored OEM BIOS identity (vendor coheres with the board; -version uses the vendor's real format) through smbios_set_defaults, so an explicit --smbios type=0 still wins and an un-seeded VM stays stock. (spoof.h include added by +Stock QEMU leaves type0 vendor/version/date unset (guest reads an empty/firmware- +default "SeaBIOS" BIOS via WMI/dmidecode) and type1 system manufacturer/product fall +back to the bare machine values. Force the persona identity through smbios_set_defaults: +- real-OEM persona: vendor-formatted BIOS + board-brand system manufacturer/product; +- hyperv persona: Microsoft "Virtual Machine". +Config (-smbios) still wins; an un-seeded VM stays stock. (spoof.h include added by 0015-smbios-vm-bit.) diff --git a/hw/smbios/smbios.c b/hw/smbios/smbios.c -index 7d71418..13ffa93 100644 +index 7d71418..8321f10 100644 --- a/hw/smbios/smbios.c +++ b/hw/smbios/smbios.c -@@ -1027,6 +1027,12 @@ void smbios_set_defaults(const char *manufacturer, const char *product, +@@ -1027,6 +1027,15 @@ void smbios_set_defaults(const char *manufacturer, const char *product, { smbios_have_defaults = true; -+ /* qemu-spoof: a real OEM BIOS identity (type0). Stock leaves vendor/version/date -+ * unset, so the guest reads an empty / firmware-default ("SeaBIOS") BIOS — a tell. -+ * Platform-anchored so the BIOS vendor coheres with the board. Inert without a seed. */ ++ /* qemu-spoof: a real OEM (or Hyper-V) BIOS + system identity. Stock leaves type0 ++ * vendor/version/date unset (guest reads empty/"SeaBIOS") and type1 falls back to ++ * the bare machine manufacturer/product; force the persona's values. Platform- ++ * anchored; config (-smbios) still wins; inert without a seed. */ + SMBIOS_SET_DEFAULT(smbios_type0.vendor, spoof_bios_vendor(NULL)); + SMBIOS_SET_DEFAULT(smbios_type0.version, spoof_bios_version(NULL)); + SMBIOS_SET_DEFAULT(smbios_type0.date, spoof_bios_date(NULL)); ++ SMBIOS_SET_DEFAULT(smbios_type1.manufacturer, spoof_system_manufacturer(NULL)); ++ SMBIOS_SET_DEFAULT(smbios_type1.product, spoof_system_product(NULL)); SMBIOS_SET_DEFAULT(smbios_type1.manufacturer, manufacturer); SMBIOS_SET_DEFAULT(smbios_type1.product, product); SMBIOS_SET_DEFAULT(smbios_type1.version, version); diff --git a/patches/README.md b/patches/README.md index 50e8344..dc8ea03 100644 --- a/patches/README.md +++ b/patches/README.md @@ -11,7 +11,7 @@ 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 | +| 0002-x86-machine-spoof-properties | register `spoof-seed` / `spoof-mode` / `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` | @@ -25,7 +25,7 @@ match exactly. Naming: `0002` = infra, `0010+` = one aspect each. | 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 | -| 0024-smbios-bios-type0 | SMBIOS type0 BIOS vendor / version / date (platform-anchored OEM firmware) | +| 0024-smbios-bios-type0 | SMBIOS type0 BIOS vendor/version/date + type1 system manufacturer/product (real-OEM or Hyper-V persona) | ## The PCI-id problem (why 0018 is careful) diff --git a/src/spoof-core.c b/src/spoof-core.c index 217295c..8c80bd1 100644 --- a/src/spoof-core.c +++ b/src/spoof-core.c @@ -15,8 +15,8 @@ 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 bool g_ready; +static char g_mode[16], 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) { @@ -37,17 +37,38 @@ static void config_load(void) return; } read_prop("spoof-seed", "QEMU_SPOOF_SEED", g_seed, sizeof(g_seed)); + read_prop("spoof-mode", "QEMU_SPOOF_MODE", g_mode, sizeof(g_mode)); 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_on(void) { return spoof_mode() != SPOOF_MODE_NONE; } bool spoof_enabled(void) { return spoof_on(); } +SpoofMode spoof_mode(void) +{ + config_load(); + if (!g_seed[0]) return SPOOF_MODE_NONE; + if (!strcmp(g_mode, "none")) return SPOOF_MODE_NONE; + if (!strcmp(g_mode, "hyperv")) return SPOOF_MODE_HYPERV; + if (!strcmp(g_mode, "vbs")) return SPOOF_MODE_VBS; + if (!strcmp(g_mode, "physical")) return SPOOF_MODE_PHYSICAL; + /* back-compat with the old spoof-hv knob (real-OEM persona either way) */ + if (!strcmp(g_hv, "hidden")) return SPOOF_MODE_PHYSICAL; + if (!strcmp(g_hv, "hyperv")) return SPOOF_MODE_VBS; + if (!strcmp(g_hv, "off")) return SPOOF_MODE_NONE; + return SPOOF_MODE_VBS; /* seeded default = VBS (real-OEM + Hyper-V) */ +} +bool spoof_persona_msvm(void) { return spoof_mode() == SPOOF_MODE_HYPERV; } +bool spoof_presence_hyperv(void) +{ + SpoofMode m = spoof_mode(); + return m == SPOOF_MODE_HYPERV || m == SPOOF_MODE_VBS; +} + /* ---- deterministic derivation: fnv1a(seed|key) -> splitmix64 -------------- */ static uint64_t fnv1a(const char *s) { @@ -94,25 +115,26 @@ int spoof_anchor_vendor(void) /* ---- 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 */ + switch (spoof_mode()) { /* derived from the mode preset */ + case SPOOF_MODE_HYPERV: + case SPOOF_MODE_VBS: return SPOOF_HV_HYPERV; /* present Hyper-V */ + case SPOOF_MODE_PHYSICAL: return SPOOF_HV_HIDDEN; /* bare metal */ + default: return SPOOF_HV_OFF; + } } 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 */ + return spoof_on(); /* any spoof mode drops WAET */ } 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 */ + return spoof_on(); /* QEMU pvpanic isn't a Hyper-V/real device */ } SpoofVgidPolicy spoof_vmgenid_policy(void) { diff --git a/src/spoof-core.h b/src/spoof-core.h index 5294621..7a9069a 100644 --- a/src/spoof-core.h +++ b/src/spoof-core.h @@ -16,7 +16,20 @@ #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #endif -bool spoof_on(void); /* seed present */ +bool spoof_on(void); /* a persona is active (seed set + mode != none) */ + +/* The master config fork. Two axes (persona x presence) as named presets: + * none stock persona + stock-kvm presence (no spoof) + * hyperv microsoft-vm persona + hyperv presence (honest Hyper-V child VM) + * vbs real-OEM persona + hyperv presence (mimic physical Win11 + VBS) + * physical real-OEM persona + bare-metal presence (VBS-off machine) */ +typedef enum { + SPOOF_MODE_NONE = 0, SPOOF_MODE_HYPERV, SPOOF_MODE_VBS, SPOOF_MODE_PHYSICAL +} SpoofMode; +SpoofMode spoof_mode(void); +bool spoof_persona_msvm(void); /* hardware persona = Microsoft "Virtual Machine" */ +bool spoof_presence_hyperv(void);/* present Hyper-V enlightenments (hyperv|vbs) */ + 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); diff --git a/src/spoof-display.c b/src/spoof-display.c index 6b395d2..075f62c 100644 --- a/src/spoof-display.c +++ b/src/spoof-display.c @@ -17,11 +17,15 @@ static const char *const EDID_NAME[] = { const char *spoof_edid_vendor(const char *def) { - return spoof_on() ? SPOOF_PICK("edid.vend", EDID_VEND) : def; + if (!spoof_on()) return def; + if (spoof_persona_msvm()) return "MSF"; /* Microsoft synthetic display */ + return SPOOF_PICK("edid.vend", EDID_VEND); } const char *spoof_edid_name(const char *def) { - return spoof_on() ? SPOOF_PICK("edid.name", EDID_NAME) : def; + if (!spoof_on()) return def; + if (spoof_persona_msvm()) return "Hyper-V Monitor"; + return SPOOF_PICK("edid.name", EDID_NAME); } uint16_t spoof_edid_model(uint16_t def) { diff --git a/src/spoof-platform.c b/src/spoof-platform.c index a8f3553..8718309 100644 --- a/src/spoof-platform.c +++ b/src/spoof-platform.c @@ -77,13 +77,15 @@ static const Board *chosen_board(void) 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; } +/* In hyperv mode the hardware persona is an honest Hyper-V "Virtual Machine" + * (real Hyper-V ACPI OEM = VRTUAL/MICROSFT, vendor = Microsoft Corporation). */ +const char *spoof_plat_acpi_oem(void) { return spoof_persona_msvm() ? "VRTUAL" : plat()->oem; } +const char *spoof_plat_acpi_table(void) { return spoof_persona_msvm() ? "MICROSFT" : plat()->table; } +const char *spoof_plat_bios_vendor(void) { return spoof_persona_msvm() ? "Microsoft Corporation" : plat()->bios; } +const char *spoof_plat_baseboard(void) { return spoof_persona_msvm() ? "Microsoft Corporation" : plat()->board; } +const char *spoof_plat_machine_desc(void) { return spoof_persona_msvm() ? "Virtual Machine" : chosen_board()->model; } +const char *spoof_plat_socket(void) { return spoof_persona_msvm() ? "None" : chosen_board()->socket; } +const char *spoof_plat_oem_string(void) { return spoof_persona_msvm() ? "Hyper-V" : plat()->oemstr; } const char *spoof_bios_vendor(const char *def) { @@ -96,6 +98,9 @@ const char *spoof_bios_version(const char *def) if (!spoof_on()) { return def; } + if (spoof_persona_msvm()) { + return "Hyper-V UEFI Release v4.1"; + } static char v[32]; const char *b = spoof_plat_bios_vendor(); if (strstr(b, "Megatrends")) { /* AMI Aptio */ @@ -136,3 +141,15 @@ const char *spoof_baseboard_manufacturer(const char *def) { return spoof_on() ? spoof_plat_baseboard() : def; } +/* SMBIOS type1 (system) — the most guest-visible identity (Win32_ComputerSystem). + * hyperv: Microsoft "Virtual Machine"; real-OEM: the board brand + model. */ +const char *spoof_system_manufacturer(const char *def) +{ + if (!spoof_on()) return def; + return spoof_persona_msvm() ? "Microsoft Corporation" : spoof_plat_baseboard(); +} +const char *spoof_system_product(const char *def) +{ + if (!spoof_on()) return def; + return spoof_persona_msvm() ? "Virtual Machine" : spoof_plat_machine_desc(); +} diff --git a/src/spoof-storage.c b/src/spoof-storage.c index f152135..d20cfaa 100644 --- a/src/spoof-storage.c +++ b/src/spoof-storage.c @@ -124,7 +124,7 @@ static void v_init(void) 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_model(const char *def) { if (!spoof_on()) return def; if (spoof_persona_msvm()) return "Virtual HD ATA Device"; 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; } @@ -155,7 +155,9 @@ void spoof_nvme_nguid(uint8_t out[16]) } const char *spoof_cdrom_model(const char *def) { - return spoof_on() ? SPOOF_PICK("cdrom.model", CDROM_MODEL) : def; + if (!spoof_on()) return def; + if (spoof_persona_msvm()) return "Msft Virtual DVD-ROM"; + return SPOOF_PICK("cdrom.model", CDROM_MODEL); } /* 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. */ diff --git a/src/spoof.h b/src/spoof.h index 6b18205..1e0ea38 100644 --- a/src/spoof.h +++ b/src/spoof.h @@ -38,6 +38,8 @@ bool spoof_pvpanic_hide(void); /* drop the pvpanic device (_HID QEMU000 const char *spoof_bios_vendor(const char *def); /* SMBIOS type0 */ const char *spoof_bios_version(const char *def); /* SMBIOS type0 (vendor-format) */ const char *spoof_bios_date(const char *def); /* SMBIOS type0 (MM/DD/YYYY) */ +const char *spoof_system_manufacturer(const char *def); /* SMBIOS type1 */ +const char *spoof_system_product(const char *def); /* SMBIOS type1 */ const char *spoof_baseboard_manufacturer(const char *def); /* SMBIOS type2 */ /* ---- ACPI (spoof-acpi.c; hw/acpi, hw/i386/acpi-build) --------------------- */